什么是线程安全(c#) ?(字符串,数组,…吗?)

本文关键字:数组 字符串 什么 线程 安全 | 更新日期: 2023-09-27 17:52:56

我对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
 }
对特定字段的写入和读取是否会导致读取损坏(字段的一半被更改,一半仍然不变)?

谢谢你的回答和时间。

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

什么是线程安全(c#) ?(字符串,数组,…吗?)

对于不同的情况是不同的,但总的来说,如果所有线程都在读取,则读取是安全的。如果有写操作,那么读写操作都是不安全的,除非可以自动地完成(在同步块内或使用原子类型)。

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

对于string,你很幸运——它们是不可变的,所以你所能做的就是读取它们。对于其他类型,当您读取它们时,您必须采取预防措施,防止它们在其他线程中更改。

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

c#语言在3.10节中保证读写在单个线程上时是一致的顺序:


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


在多线程、多处理器系统中,事件之间的时间顺序并不一定是一致的。 c#语言不保证有一致的排序。只要不涉及关键执行点,一个线程观察到的写顺序可能与另一个线程观察到的顺序完全不同。

这个问题因此无法回答,因为它包含一个未定义的词。你能给一个精确的定义吗?在多线程、多处理器系统中对事件的理解是什么?

该语言保证副作用仅根据关键的执行点排序,即使如此,当涉及异常时也不做任何强有力的保证。再次引用3.10节的内容:


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

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


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

By "thread safe"你的意思是两个线程在读取列表时总是观察到一致的结果吗?如上所述,c#语言在读取变量时对观察结果的保证非常有限。你能给一个"线程安全"的精确定义吗?对你来说是非易失性读数意味着什么?

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

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

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

是的。我建议您参考第5.5节,其中规定:


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


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

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

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

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

这将在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.

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

p

通过Interlocked.Increment(ref i)

可以实现线程安全的增量