【问题标题】:What is thread safe (C#) ? (Strings, arrays, ... ?)什么是线程安全(C#)? (字符串,数组,...?)
【发布时间】:2011-08-13 05:40:48
【问题描述】:

我对 C# 很陌生,所以请多多包涵。我对线程安全有点困惑。什么时候是线程安全的,什么时候不是?

读取(只是从之前初始化的内容中读取)是否总是线程安全的?

//EXAMPLE
RSACryptoServiceProvider rsa = new RSACrytoServiceProvider();
rsa.FromXmlString(xmlString);  
//Is this thread safe if xml String is predifined 
//and this code can be called from multiple threads?

访问数组或列表中的对象是否总是线程安全的(如果您使用 for 循环进行枚举)?

//EXAMPLE (a is local to thread, array and list are global)
int a = 0;
for(int i=0; i<10; i++)
{
  a += array[i];
  a -= list.ElementAt(i);
}

枚举总是/永远是线程安全的吗?

//EXAMPLE
foreach(Object o in list)
{
   //do something with o
 }

写入和读取特定字段是否会导致读取损坏(字段的一半已更改,而另一半仍保持不变)?

感谢您的所有回答和时间。

编辑:我的意思是如果所有线程都只是读取和使用(而不是写入或更改)对象。 (除了最后一个问题,很明显我的意思是线程是否既读又写)。因为我不知道普通访问或枚举是否是线程安全的。

【问题讨论】:

    标签: c# arrays string thread-safety enumeration


    【解决方案1】:

    不同的情况不同,但总的来说,如果所有线程都在读取,读取是安全的。如果有任何正在写入,则读取或写入都不是安全的,除非它可以原子地完成(在同步块内或使用原子类型)。

    不确定读取是否正常——你永远不知道幕后发生了什么——例如,getter 可能需要在首次使用时初始化数据(因此写入本地字段)。

    对于字符串,你很幸运——它们是不可变的,所以你所能做的就是阅读它们。对于其他类型,您必须采取预防措施,防止它们在阅读时在其他线程中发生变化。

    【讨论】:

      【解决方案2】:

      从字段中读取(只是从之前初始化的内容中读取)总是线程安全的吗?

      在 3.10 节中,当读取和写入在单个线程上时,C# 语言保证读取和写入的顺序一致:


      数据依赖性保留在执行线程中。也就是说,计算每个变量的值,就好像线程中的所有语句都按原始程序顺序执行一样。保留初始化排序规则。


      多线程、多处理器系统中的事件在时间上不一定具有明确定义的一致顺序。 C# 语言不保证存在一致的顺序。只要不涉及关键执行点,一个线程观察到的一系列写入操作可能与另一个线程观察到的顺序完全不同。

      这个问题因此无法回答,因为它包含一个未定义的单词。对于多线程、多处理器系统中的事件,您能否准确定义“之前”对您的意义?

      该语言保证仅针对关键执行点对副作用进行排序,即便如此,在涉及异常时也不做出任何强有力的保证。再次引用第 3.10 节:


      继续执行 C# 程序,以便在关键执行点保留每个执行线程的副作用。副作用定义为对易失性字段的读取或写入、对非易失性变量的写入、对外部资源的写入以及抛出异常。必须保留这些副作用的顺序的关键执行点是对易失性字段、锁定语句以及线程创建和终止的引用。 [...] 对于易失性读取和写入,副作用的顺序得以保留。

      此外,如果执行环境可以推断出该表达式的值未被使用并且没有产生所需的副作用(包括由调用方法或访问易失性字段引起的任何副作用),则执行环境不需要评估该表达式的一部分。当程序执行被异步事件(例如另一个线程抛出异常)中断时,不保证可观察到的副作用在原始程序顺序中是可见的。


      从数组或列表访问对象是否总是线程安全的(如果您使用 for 循环进行枚举)?

      “线程安全”是指两个线程在从列表中读取时始终观察到一致的结果?如上所述,C# 语言对读取变量时的结果观察做出非常有限的保证。关于非易失性读取,您能否准确定义“线程安全”对您意味着什么?

      枚举总是/永远是线程安全的吗?

      即使在单线程场景中,在枚举集合时修改集合也是非法的。在多线程场景中这样做肯定是不安全的。

      对特定字段的写入和读取是否会导致读取损坏(字段的一半已更改,而另一半仍未更改)?

      是的。请参阅第 5.5 节,其中指出:


      以下数据类型的读写是原子的:bool、char、byte、sbyte、short、ushort、uint、int、float 和引用类型。此外,上一个列表中具有基础类型的枚举类型的读取和写入也是原子的。其他类型的读取和写入,包括 long、ulong、double 和 decimal,以及用户定义的类型,不保证是原子的。除了为此目的设计的库函数之外,不保证原子性的读-修改-写,例如在递增或递减的情况下。


      【讨论】:

      • “之前”是指在线程启动之前初始化了一些东西。通过“线程安全”,我的意思是如果许多线程可以枚举和/或迭代相同的对象(数组,列表,......
      • @Ben:如果那里有一个线程开始,那么就有一个关键执行点,因此可以保证观察到的副作用是相对于该关键点进行排序的。
      • 不能保证 64 位架构上的 long/ulong 读/写是原子的?
      • @James:语言规范对 64 位硬件的主题没有什么可说的。它只是说必须保证 32 位变量和引用大小的变量是原子的,以使实现符合要求。如果您个人碰巧在硬件上运行了一个 C# 程序,该硬件提供了更强的保证,那么该保证是由您的硬件供应商提供给您的,而不是由您友好的邻居 C# 编译器提供商提供的。我不会代表不知名的硬件制造商提出任何声明。
      • 同样,x86 内存模型远比我上面概述的内存模型强得多。 x86 硬件供应商正在做出他们声称要兑现的承诺。 C# 语言当然不代表 x86 硬件供应商做出任何承诺。如果您选择依赖硬件供应商对您的承诺,那是您的事。
      【解决方案3】:

      好吧,我通常认为一切都是线程不安全的。为了在线程环境中快速而肮脏地访问全局对象,我使用 lock(object) 关键字。 .Net 有一套广泛的同步方法,如不同的信号量等。

      【讨论】:

        【解决方案4】:

        如果有任何线程正在写入,则读取可能是线程不安全的(例如,如果它们在读取过程中写入,它们将被异常击中)。 p>

        如果你必须这样做,那么你可以这样做:

        lock(i){
            i.GetElementAt(a)
        }
        

        这将在 i 上强制线程安全(只要其他线程在使用它之前类似地尝试锁定 i。一次只能锁定一个引用类型。

        关于枚举,我会参考MSDN:

        The enumerator does not have exclusive access to the collection; therefore, enumerating 
        through a collection is intrinsically not a thread-safe procedure. To guarantee thread 
        safety during enumeration, you can lock the collection during the entire enumeration. To 
        allow the collection to be accessed by multiple threads for reading and writing, you must     
        implement your own synchronization.
        

        【讨论】:

          【解决方案5】:

          没有线程安全的示例:当多个线程递增一个整数时。您可以通过预先确定的增量数量来设置它。不过,您可能会观察到,int 并未像您想象的那样增加。发生的情况是两个线程可能会增加相同的整数值。这只是在处理多个线程时可能会观察到的过多效果的一个示例。

          附言

          可通过Interlocked.Increment(ref i) 获得线程安全增量

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-11-30
            • 2011-08-16
            • 1970-01-01
            • 2015-09-05
            • 1970-01-01
            • 1970-01-01
            • 2010-12-10
            相关资源
            最近更新 更多