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

时间:2021-04-29 11:46:36

I'm quite new to C# so please bear with me. I'm a bit confused with the thread safety. When is something thread safe and when something isn't?

我对c#很陌生,所以请容忍我。我对线程安全有点困惑。什么时候线程是安全的,什么时候不是?

Is reading (just reading from something that was initialized before) from a field always thread safe?

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

//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?

Is accessing an object from an array or list always thread safe (in case you use a for loop for enumeration)?

从数组或列表访问对象总是线程安全的(如果您使用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);
}

Is enumeration always/ever thread safe?

枚举总是/曾经是线程安全的吗?

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

Can writing and reading to a particular field ever result in a corrupted read (half of the field is changed and half is still unchanged) ?

写入和读取到某个特定的字段是否会导致错误的读取(字段的一半被更改,一半仍然没有更改)?

Thank you for all your answers and time.

谢谢你的回答和时间。

EDIT: I meant if all threads are only reading & using (not writing or changing) object. (except for the last question where it is obvious that I meant if threads both read and write). Because I do not know if plain access or enumeration is thread safe.

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

5 个解决方案

#1


26  

It's different for different cases, but in general, reading is safe if all threads are reading. If any are writing, neither reading or writing is safe unless it can be done atomically (inside a synchronized block or with an atomic type).

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

It isn't definite that reading is ok -- you never know what is happening under the hoods -- for example, a getter might need to initialize data on first usage (therefore writing to local fields).

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

For Strings, you are in luck -- they are immutable, so all you can do is read them. With other types, you will have to take precautions against them changing in other threads while you are reading them.

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

#2


13  

Is reading (just reading from something that was initialized before) from a field always thread safe?

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

The C# language guarantees that reads and writes are consistently ordered when the reads and writes are on a single thread in section 3.10:

c#语言保证当读和写在一个线程上时,读和写的顺序是一致的。


Data dependence is preserved within a thread of execution. That is, the value of each variable is computed as if all statements in the thread were executed in original program order. Initialization ordering rules are preserved.

数据依赖关系保存在执行线程中。也就是说,每个变量的值被计算为线程中的所有语句按照原始程序顺序执行。保持初始化排序规则。


Events in a multithreaded, multiprocessor system do not necessarily have a well-defined consistent ordering in time with respect to each other. The C# language does not guarantee there to be a consistent ordering. A sequence of writes observed by one thread may be observed to be in a completely different order when observed from another thread, so long as no critical execution point is involved.

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

The question is therefore unanswerable because it contains an undefined word. Can you give a precise definition of what "before" means to you with respect to events in a multithreaded, multiprocessor system?

因此,这个问题是无法回答的,因为它包含一个没有定义的词。对于多线程、多处理器系统中的事件,您能否给出“before”对您的确切定义?

The language guarantees that side effects are ordered only with respect to critical execution points, and even then, does not make any strong guarantees when exceptions are involved. Again, to quote from section 3.10:

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


Execution of a C# program proceeds such that the side effects of each executing thread are preserved at critical execution points. A side effect is defined as a read or write of a volatile field, a write to a non-volatile variable, a write to an external resource, and the throwing of an exception. The critical execution points at which the order of these side effects must be preserved are references to volatile fields, lock statements, and thread creation and termination. [...] The ordering of side effects is preserved with respect to volatile reads and writes.

执行c#程序时,每个执行线程的副作用都保留在关键的执行点上。副作用定义为对易失性字段的读或写、对非易失性变量的写、对外部资源的写和抛出异常。必须保持这些副作用的顺序的关键执行点是对volatile字段、锁语句和线程创建和终止的引用。[…[参考译文]对于易失性读写,副作用的顺序是保留的。

Additionally, the execution environment need not evaluate part of an expression if it can deduce that that expression’s value is not used and that no needed side effects are produced (including any caused by calling a method or accessing a volatile field). When program execution is interrupted by an asynchronous event (such as an exception thrown by another thread), it is not guaranteed that the observable side effects are visible in the original program order.

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


Is accessing an object from an array or list always thread safe (in case you use a for loop for enumeration)?

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

By "thread safe" do you mean that two threads will always observe consistent results when reading from a list? As noted above, the C# language makes very limited guarantees about observation of results when reading from variables. Can you give a precise definition of what "thread safe" means to you with respect to non-volatile reading?

“线程安全”的意思是两个线程在从列表中读取时总是会观察到一致的结果吗?如上所述,c#语言在从变量中读取结果时,对观察结果的保证非常有限。对于非易失性阅读,您能给出“线程安全”对您意味着什么吗?

Is enumeration always/ever thread safe?

枚举总是/曾经是线程安全的吗?

Even in single threaded scenarios it is illegal to modify a collection while enumerating it. It is certainly unsafe to do so in multithreaded scenarios.

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

Can writing and reading to a particular field ever result in a corrupted read (half of the field is changed and half is still unchanged) ?

写入和读取到某个特定的字段是否会导致错误的读取(字段的一半被更改,一半仍然没有更改)?

Yes. I refer you to section 5.5, which states:

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


Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types. In addition, reads and writes of enum types with an underlying type in the previous list are also atomic. Reads and writes of other types, including long, ulong, double, and decimal, as well as user-defined types, are not guaranteed to be atomic. Aside from the library functions designed for that purpose, there is no guarantee of atomic read-modify-write, such as in the case of increment or decrement.

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


#3


4  

Well, I generally assume everything is thread unsafe. For quick and dirty access to global objects in an threaded environment I use the lock(object) keyword. .Net have an extensive set of synchronization methods like different semaphores and such.

我通常认为所有的东西都是不安全的。为了在线程环境中快速且不受限制地访问全局对象,我使用了lock(object)关键字。

#4


3  

Reading can be thread-unsafe if there are any threads that are writing (if they write in the middle of a read, for example, they'll be hit with an exception).

如果有正在写入的线程,那么读取可能是线程不安全的(例如,如果它们在读取过程中写入,那么它们将会受到异常的影响)。

If you must do this, then you can do:

如果你必须这样做,你就能做到:

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

This will force thread-safety on i (as long as other threads similarly attempt to lock i before they use it. only one thing can lock a reference type at a time.

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

In terms of enumeration, I'll refer to the MSDN:

在列举方面,我将参考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


0  

An example of no thread-safety: When several threads increment an integer. You can set it up in a way that you have a predeterminded number of increments. What youmay observe though, is, that the int has not been incremented as much as you thought it would. What happens is that two threads may increment the same value of the integer.This is but an example of aplethora of effects you may observe when working with several threads.

一个没有线程安全性的例子:当几个线程增加一个整数。你可以以一种预先确定的增量的方式来设置它。但是,您可能会观察到,int并没有您认为的那样增加。发生的情况是,两个线程可以增加整数的相同值。这只是使用几个线程时可能会观察到的效应的一个比较典型的例子。

PS

PS

A thread-safe increment is available through Interlocked.Increment(ref i)

通过互锁可以获得线程安全的增量。增量(ref)

#1


26  

It's different for different cases, but in general, reading is safe if all threads are reading. If any are writing, neither reading or writing is safe unless it can be done atomically (inside a synchronized block or with an atomic type).

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

It isn't definite that reading is ok -- you never know what is happening under the hoods -- for example, a getter might need to initialize data on first usage (therefore writing to local fields).

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

For Strings, you are in luck -- they are immutable, so all you can do is read them. With other types, you will have to take precautions against them changing in other threads while you are reading them.

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

#2


13  

Is reading (just reading from something that was initialized before) from a field always thread safe?

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

The C# language guarantees that reads and writes are consistently ordered when the reads and writes are on a single thread in section 3.10:

c#语言保证当读和写在一个线程上时,读和写的顺序是一致的。


Data dependence is preserved within a thread of execution. That is, the value of each variable is computed as if all statements in the thread were executed in original program order. Initialization ordering rules are preserved.

数据依赖关系保存在执行线程中。也就是说,每个变量的值被计算为线程中的所有语句按照原始程序顺序执行。保持初始化排序规则。


Events in a multithreaded, multiprocessor system do not necessarily have a well-defined consistent ordering in time with respect to each other. The C# language does not guarantee there to be a consistent ordering. A sequence of writes observed by one thread may be observed to be in a completely different order when observed from another thread, so long as no critical execution point is involved.

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

The question is therefore unanswerable because it contains an undefined word. Can you give a precise definition of what "before" means to you with respect to events in a multithreaded, multiprocessor system?

因此,这个问题是无法回答的,因为它包含一个没有定义的词。对于多线程、多处理器系统中的事件,您能否给出“before”对您的确切定义?

The language guarantees that side effects are ordered only with respect to critical execution points, and even then, does not make any strong guarantees when exceptions are involved. Again, to quote from section 3.10:

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


Execution of a C# program proceeds such that the side effects of each executing thread are preserved at critical execution points. A side effect is defined as a read or write of a volatile field, a write to a non-volatile variable, a write to an external resource, and the throwing of an exception. The critical execution points at which the order of these side effects must be preserved are references to volatile fields, lock statements, and thread creation and termination. [...] The ordering of side effects is preserved with respect to volatile reads and writes.

执行c#程序时,每个执行线程的副作用都保留在关键的执行点上。副作用定义为对易失性字段的读或写、对非易失性变量的写、对外部资源的写和抛出异常。必须保持这些副作用的顺序的关键执行点是对volatile字段、锁语句和线程创建和终止的引用。[…[参考译文]对于易失性读写,副作用的顺序是保留的。

Additionally, the execution environment need not evaluate part of an expression if it can deduce that that expression’s value is not used and that no needed side effects are produced (including any caused by calling a method or accessing a volatile field). When program execution is interrupted by an asynchronous event (such as an exception thrown by another thread), it is not guaranteed that the observable side effects are visible in the original program order.

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


Is accessing an object from an array or list always thread safe (in case you use a for loop for enumeration)?

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

By "thread safe" do you mean that two threads will always observe consistent results when reading from a list? As noted above, the C# language makes very limited guarantees about observation of results when reading from variables. Can you give a precise definition of what "thread safe" means to you with respect to non-volatile reading?

“线程安全”的意思是两个线程在从列表中读取时总是会观察到一致的结果吗?如上所述,c#语言在从变量中读取结果时,对观察结果的保证非常有限。对于非易失性阅读,您能给出“线程安全”对您意味着什么吗?

Is enumeration always/ever thread safe?

枚举总是/曾经是线程安全的吗?

Even in single threaded scenarios it is illegal to modify a collection while enumerating it. It is certainly unsafe to do so in multithreaded scenarios.

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

Can writing and reading to a particular field ever result in a corrupted read (half of the field is changed and half is still unchanged) ?

写入和读取到某个特定的字段是否会导致错误的读取(字段的一半被更改,一半仍然没有更改)?

Yes. I refer you to section 5.5, which states:

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


Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types. In addition, reads and writes of enum types with an underlying type in the previous list are also atomic. Reads and writes of other types, including long, ulong, double, and decimal, as well as user-defined types, are not guaranteed to be atomic. Aside from the library functions designed for that purpose, there is no guarantee of atomic read-modify-write, such as in the case of increment or decrement.

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


#3


4  

Well, I generally assume everything is thread unsafe. For quick and dirty access to global objects in an threaded environment I use the lock(object) keyword. .Net have an extensive set of synchronization methods like different semaphores and such.

我通常认为所有的东西都是不安全的。为了在线程环境中快速且不受限制地访问全局对象,我使用了lock(object)关键字。

#4


3  

Reading can be thread-unsafe if there are any threads that are writing (if they write in the middle of a read, for example, they'll be hit with an exception).

如果有正在写入的线程,那么读取可能是线程不安全的(例如,如果它们在读取过程中写入,那么它们将会受到异常的影响)。

If you must do this, then you can do:

如果你必须这样做,你就能做到:

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

This will force thread-safety on i (as long as other threads similarly attempt to lock i before they use it. only one thing can lock a reference type at a time.

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

In terms of enumeration, I'll refer to the MSDN:

在列举方面,我将参考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


0  

An example of no thread-safety: When several threads increment an integer. You can set it up in a way that you have a predeterminded number of increments. What youmay observe though, is, that the int has not been incremented as much as you thought it would. What happens is that two threads may increment the same value of the integer.This is but an example of aplethora of effects you may observe when working with several threads.

一个没有线程安全性的例子:当几个线程增加一个整数。你可以以一种预先确定的增量的方式来设置它。但是,您可能会观察到,int并没有您认为的那样增加。发生的情况是,两个线程可以增加整数的相同值。这只是使用几个线程时可能会观察到的效应的一个比较典型的例子。

PS

PS

A thread-safe increment is available through Interlocked.Increment(ref i)

通过互锁可以获得线程安全的增量。增量(ref)