什么是线程安全(C#)? (字符串,数组,......?)

时间:2011-04-26 17:23:07

标签: c# arrays string thread-safety enumeration

我对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
 }

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

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

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

5 个答案:

答案 0 :(得分:26)

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

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

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

答案 1 :(得分:13)

  

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

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


  

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


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

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

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


  

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

     

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


  

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

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

  

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

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

  

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

是。我引用你的第5.5节,其中说明:


  

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


答案 2 :(得分:4)

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

答案 3 :(得分:3)

如果有任何正在编写的线程,读可以是线程不安全的(如果他们在读取过程中写入,例如,他们将被异常命中)。

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

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.

答案 4 :(得分:0)

无线程安全的示例:当多个线程递增整数时。您可以通过预先确定的增量数来设置它。你可以观察到的是,int并没有像你想象的那样增加。会发生什么是两个线程可能会增加整数的相同值。这只是您在使用多个线程时可能会遇到的各种影响的示例。

<强> PS

通过Interlocked.Increment(ref i)

可以获得线程安全增量