官术网_书友最值得收藏!

The System.Threading.Interlocked class

When we reviewed race conditions in the previous chapter, we learned that even a simple increment operation consists of three separate actions. Although modern CPUs can perform such operations at once, it is necessary to make them safe to be used in concurrent programs.

The .NET Framework contains the System.Threading.Interlocked class that provides access to several operations that are atomic, which means that they are uninterruptible and appear to occur instantaneously to the rest of the system. These are the operations that the lock-free algorithms are based on.

Let's revise a race condition example and compare the locking and Interlocked class operations. First, we will use the traditional locking approach:

var counterLock = new object();
var counter = 0;
ThreadStart proc =
  () =>
  {
    for (int i = 0; i < count; i++)
    {
      lock (counterLock)
        counter++;
      Thread.SpinWait(100);
      lock (counterLock)
        counter--;
    }
  };
var threads =
  Enumerable
    .Range(0, 8)
    .Select(n => new Thread(proc))
    .ToArray();
var sw = Stopwatch.StartNew();
foreach (var thread in threads)
  thread.Start();
foreach (var thread in threads)
  thread.Join();
sw.Stop();
Console.WriteLine("Locks: counter={0}, time = {1}ms", counter, sw.ElapsedMilliseconds);

Now, let's replace locking with the Interlocked class method calls:

counter = 0;
ThreadStart proc2 =
  () =>
  {
    for (int i = 0; i < count; i++)
    {
      Interlocked.Increment(ref counter);
      Thread.SpinWait(100);
      Interlocked.Decrement(ref counter);
    }
  };
threads =
  Enumerable
    .Range(0, 8)
    .Select(n => new Thread(proc2))
    .ToArray();
sw = Stopwatch.StartNew();
foreach (var thread in threads)
  thread.Start();
foreach (var thread in threads)
  thread.Join();
sw.Stop();
Console.WriteLine("Lock free: counter={0}, time = {1}ms", counter, sw.ElapsedMilliseconds);

As a result, we got this on a reference computer:

Locks: counter=0, time = 1892ms
Locks: counter=0, time = 800ms

Just using atomic operations performed more than twice as well and kept the program logic correct.

Another tricky part is 64-bit integer calculations. When the program runs in the 64-bit mode, the read and write operations for 64-bit integer numbers are atomic. However, when running in the 32-bit mode, these operations become nonatomic and consist of two parts—reading/writing high 32 bits and low 32 bits of the number.

The Interlocked class contains the Read method that can read a 64-bit integer in the 32-bit mode as an atomic operation. This is not required in 64-bit mode, but if you compile your program in any CPU mode then you should use this method to guarantee atomicity of reads. There are the Increment and Decrement method overloads for 64-bit integers as well, and there is the Add method that allows us to have atomic addition of 32-bit and 64-bit integers.

Another very important operation is the value exchange. Looking at the following code it is obvious that this operation is not atomic, and thus we must put this code inside some kind of lock to keep this operation correct in a concurrent program:

var tmp = a;
a = b;
b = tmp;

The Interlocked class allows us to perform this operation as atomic with the Exchange method:

b = Interlocked.Exchange(ref a, b)

There are several overloads for this method that allow us to exchange the numeric values of different types including 32-bit and 64-bit integers, the float and double values, object references (there is a generic version of this method with the type parameter), and the IntPtr structures.

The most complicated atomic operation provided by the Interlocked class is the CompareExchange method. It accepts three arguments, then it compares the first argument with the third; if they are equal, it assigns the second argument value to the first argument. This is performed by special instruction on hardware too. We will see an example of this later in this chapter when we try to implement a lock-free queue.

Note

All the Interlocked class method calls implicitly generate full fences.

主站蜘蛛池模板: 鄄城县| 苍梧县| 武胜县| 塔河县| 扎囊县| 寿宁县| 康乐县| 扎兰屯市| 平远县| 玉屏| 萍乡市| 峡江县| 南岸区| 南江县| 永兴县| 康乐县| 辽宁省| 东海县| 盐城市| 垣曲县| 新干县| 东台市| 兴和县| 平山县| 太原市| 彩票| 濮阳市| 东源县| 肇东市| 松原市| 石林| 图木舒克市| 永吉县| 曲水县| 玉田县| 富民县| 巴东县| 怀安县| 洪泽县| 石楼县| 大余县|