Deep into multithreading: an in depth analysis of Interlocked

  • 2020-05-12 03:02:02
  • OfStack

On most computers, the variable increment operation is not an atomic operation. The following steps are required:

1: load the value from the instance variable into the register.

2: increase or decrease the value.

3: store the value in an instance variable.

In a multithreaded environment, threads are preempted after performing the first two steps. All three steps are then executed by another thread, and when the first thread resumes execution, it overrides the value in the instance variable, causing the second thread to lose the result of the increment or decrement.

Interlocked can provide atomic operations on variables Shared by multiple threads.

Interlocked.Increment: increments the value of a specified variable and stores the result as an atomic operation.
Interlocked. Decrement decrements the value of a specified variable and stores the result as an atomic operation.
Interlocked.Add in the form of an atomic operation, adds two integers and replaces the first integer with the sum of both

But Interlocked does not provide atomic operations for multiplication and division. So how do you implement multiplication, division, and atomic support for other non-atomic operations?


The key is Interlocked.CompareExchange,Jeffrey Richter calls it InterLocked Anything mode.

Next, we use Interlocked.CompareExchange to realize the atomic operation of maximization.


public static int Maximum(ref int target, int value)
        {
            int currentVal = target;   // will target Save the current value to currentVal In the 
            int startVal, desiredVal;  // Declare two variables to record the value before the operation begins and the expected result value. 
            do
            {
                startVal = currentVal; // will currentVal Save the value to startVal , which is recorded at this time target The initial value before the operation begins. 
                desiredVal = Math.Max(startVal, value); // through startVal Perform complex calculations and return 1 In this case, only the maximum of the two is returned. 
                // Threads may be preempted here ,target The value may be changed 
                // if target The value of theta has been changed, so target and startVal I don't want to wait, so I shouldn't use it desiredVal replace target.
                // if target Is not changed, then target and startVal The value of desiredVal replace target.
                // Whether you replace it or not, CompareExchange The return value of target Theta, so theta currentVal The value of theta is now zero target The latest value of. 
                //CompareExchange: will target and startVal Is used when the value of desiredVal Replace, otherwise no operation, 
                // Whether you replace it or not, what you return is the original saved in target The value of the. 
                currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal);
            } while (startVal != currentVal); // when target When the starting value and the latest value of target Has been modified, so proceed to next time, otherwise exit the loop. 
            return desiredVal;
        }

The core of this code is: currentVal = Interlocked.CompareExchange (ref target, desiredVal, startVal);
// compare the value of target with the value of startVal. If the value is equal, replace target with desiredVal. Otherwise, no operation is performed.
// whether replaced or not, the original value saved in target will be returned.

Here, the computation can be complex, unlike Math.Max1 above, so you can encapsulate it with a delegate call.


delegate int Morpher<TResult, TArgument>(int startValue, TArgument argument,
            out TResult morphResult);
        static TResult Morph<TResult, TArgument>(ref int target, TArgument argument,
            Morpher<TResult, TArgument> morpher)
        {
            TResult morphResult;
            int currentVal = target, startVal, desiredVal;
            do
            {
                startVal = currentVal;
                desiredVal = morpher(startVal, argument, out morphResult);
                currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal);
            } while (startVal != currentVal);
            return morphResult;
        }

The basic principle is 1 to the top.


Related articles: