When you are using the task parallel library to parallelise your code, or when writing multi-threaded applications, you must ensure that your code is thread-safe. This means that any data that is shared by multiple threads must be accessed in a manner that ensures that threads cannot interfere with each other's results.
Some standard operations provided by C# and the .NET framework are not thread-safe, even though they may appear to be. For example, you might assume that the increment operator(++) performs an atomic operation. However, in reality incrementing a variable's value involves reading the value, updating the read value and writing the updated value back into the variable. If two threads attempt to increment a value simultaneously a race condition may occur and it is possible that the value be increased only by one, rather than two.
To demonstrate this problem, and for the other examples in this article, we will use methods from the Parallel class. To begin, create a console application and add the following using directives to the code. NB: If you are using an earlier version of the .NET framework than 4.0, you can create similar examples using standard multi-threading code. (4.0之前的版本没有并行计算功能)
The following code executes a parallel for loop with one million increment operations, each changing the value of a shared variable. The final value of the variable should be one million. However, on a computer with multiple cores or processors, you will likely see a much lower result, caused by the increment operator not being thread-safe.
The Interlocked class provides a number of static methods that perform atomic operations. These can generally be regarded as thread-safe. The class is found in the System.Threading namespace.
Two commonly used Interlocked methods are Increment and Decrement. As their names suggest, these methods increase or decrease a variable's value by one, in a similar manner to the increment (++) and decrement (--) operators. Both methods work with either a 32-bit or 64-bit integer, which is passed to the method using a reference parameter.
The code below shows a thread-safe version of the earlier example. In this parallel loop the million operations produce the correct result.
The Add method was introduced with the .NET framework version 2.0. It performs an atomic, thread-safe addition of two values. The first parameter accepts a 32-bit or 64-bit integer variable, passed by reference. The second parameter accepts a second integer, which will be added to the first.
The sample below shows the Add method being used successfully within a parallel loop.
The Exchange method originally worked with 32-bit integers, single-precision floating-point numbers or objects. This was extended to a greater choice of data types, including generic types, with .NET 2.0. The method accepts two arguments of the same type. The first, passed by reference, is changed to match that of the second and the original value is returned.
The code below shows the syntax of the method and some sample results.
CompareExchange provides a similar operation to Exchange, in that it can replace one variable's value with an alternative value as an atomic operation. The difference is that the exchange is only made if a comparison with a third value determines equality.
The first parameter is the value that may be replaced. It is passed by reference to allow the original variable to be updated. The second parameter holds the value that the first may be replaced with. The third argument specifies a value to be compared with that of the first. If the two values match, the exchange is made. If not, the original value remains. In either case, the return value of the method is the original value of the first parameter.
The sample code below shows the CompareExchange method being called twice. In the first instance the comparison yields a match so the original value is replaced. In the second call the values do not match so the primary value remains the same.
Surprisingly, reading the value of a 64-bit integer is not an atomic operation and, therefore, is not thread-safe. When executing code on a 64-bit processor such reads are atomic and thread-safe. However, when the computer has a 32-bit processor such a read requires separate 32-bit reads from two locations. This means that it is possible, if one thread changes the 64-bit value whilst another is reading it, that simply reading a 64-bit value will yield an incorrect result.
The Read method is available in the .NET framework version 2.0 and later. It ensures that when you read a 64-bit value that you do so in a thread-safe manner. The syntax of the method is as follows:
All of the Interlocked methods are thread-safe with respect to each other. However, they do not guarantee thread safety when used with other operations. For example, if one thread uses the Increment method whilst another uses the increment operator, it is still possible to get incorrect results. Therefore, when you are using Interlocked methods against shared data you should ensure that all access to that data uses Interlocked methods. If this is not viable, you should use means to synchronise your threads, such as locking critical sections with the lock statement.
看了上面的这些知识给我感觉,其实.net编程中,存在很多多线程的陷井,只是平时没有意识到罢了。