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

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.

主站蜘蛛池模板: 怀安县| 保靖县| 长宁县| 金山区| 九江市| 五大连池市| 红安县| 尉氏县| 盐亭县| 揭东县| 南溪县| 惠安县| 昌乐县| 睢宁县| 萨迦县| 郯城县| 沧州市| 延长县| 谷城县| 泰安市| 深州市| 珠海市| 武威市| 武穴市| 灵寿县| 博罗县| 盈江县| 依兰县| 静宁县| 巫溪县| 扬州市| 秭归县| 伊宁县| 彭水| 富锦市| 高邮市| 南城县| 宜城市| 石首市| 洱源县| 内江市|