Interlocked

The Interlocked methods CompareExchange, Decrement, Exchange, and Increment provide a simple mechanism for synchronizing access to a variable that is shared by multiple threads. The threads of different processes can use this mechanism if the variable is in shared memory.

The Increment and Decrement functions combine the operations of incrementing or decrementing the variable and checking the resulting value. This atomic operation is useful in a multitasking operating system, in which the system can interrupt one thread's execution to grant a slice of processor time to another thread. Without such synchronization, one thread could increment a variable but be interrupted by the system before it could check the resulting value of the variable. A second thread could then increment the same variable. When the first thread receives its next time slice, it will check the value of the variable, which has now been incremented not once but twice. The Interlocked variable access functions protect against this kind of error.

The Exchange function atomically exchanges the values of the specified variables. The CompareExchange function combines two operations: comparing two values and storing a third value in one of the variables, based on the outcome of the comparison. CompareExchange can be used to protect computations that are more complicated than simple increment and decrement. The following example demonstrates a thread-safe method that adds to a running total.

Imports System.Threading

Public Class ThreadSafe
    ' Field totalValue contains a running total that can be updated
    ' by multiple threads. It must be protected from unsynchronized 
    ' access.
    Private totalValue As Integer = 0

    ' The Total property returns the running total.
    Public ReadOnly Property Total As Integer
        Get
            Return totalValue
        End Get
    End Property

    ' AddToTotal safely adds a value to the running total.
    Public Function AddToTotal(ByVal addend As Integer) As Integer
        Dim initialValue, computedValue As Integer
        Do
            ' Save the current running total in a local variable.
            initialValue = totalValue

            ' Add the new value to the running total.
            computedValue = initialValue + addend

            ' CompareExchange compares totalValue to initialValue. If
            ' they are not equal, then another thread has updated the
            ' running total since this loop started. CompareExchange
            ' does not update totalValue. CompareExchange returns the
            ' contents of totalValue, which do not equal initialValue,
            ' so the loop executes again.
        Loop While initialValue <> Interlocked.CompareExchange( _
            totalValue, computedValue, initialValue)
        ' If no other thread updated the running total, then 
        ' totalValue and initialValue are equal when CompareExchange
        ' compares them, and computedValue is stored in totalValue.
        ' CompareExchange returns the value that was in totalValue
        ' before the update, which is equal to initialValue, so the 
        ' loop ends.

        ' The function returns computedValue, not totalValue, because
        ' totalValue could be changed by another thread between
        ' the time the loop ends and the function returns.
        Return computedValue
    End Function
End Class

[C#]
using System.Threading;

public class ThreadSafe {
    // totalValue contains a running total that can be updated
    // by multiple threads. It must be protected from unsynchronized 
    // access.
    private int totalValue = 0;

    // The Total property returns the running total.
    public int Total {
        get { return totalValue; }
    }

    // AddToTotal safely adds a value to the running total.
    public int AddToTotal(int addend) {
        int initialValue, computedValue;
        do {
            // Save the current running total in a local variable.
            initialValue = totalValue;

            // Add the new value to the running total.
            computedValue = initialValue + addend;

            // CompareExchange compares totalValue to initialValue. If
            // they are not equal, then another thread has updated the
            // running total since this loop started. CompareExchange
            // does not update totalValue. CompareExchange returns the
            // contents of totalValue, which do not equal initialValue,
            // so the loop executes again.
        } while (initialValue != Interlocked.CompareExchange(
            ref totalValue, computedValue, initialValue));
        // If no other thread updated the running total, then 
        // totalValue and initialValue are equal when CompareExchange
        // compares them, and computedValue is stored in totalValue.
        // CompareExchange returns the value that was in totalValue
        // before the update, which is equal to initialValue, so the 
        // loop ends.

        // The function returns computedValue, not totalValue, because
        // totalValue could be changed by another thread between
        // the time the loop ends and the function returns.
        return computedValue;
    }
}

On modern processors, the methods of the Interlocked class can often be implemented by a single instruction. Thus, the methods of the Interlocked class provide very high-performance synchronization, and can be used to build higher-level synchronization mechanisms, like spin locks.

The Exchange and CompareExchange methods have overloads that take arguments of type Object. The first argument of each of these overloads is ref Object (ByRef ... As Object in Visual Basic), and type safety requires the variable passed to this argument to be typed strictly as Object; you cannot simply cast the first argument to type Object when calling these methods. The following example shows a property of type ClassA that can be set only once.

public class ClassB {
    // The private field that stores the value for the
    // ClassA property is intialized to null. It is set
    // once, from any of several threads. The field must
    // be of type Object, so that CompareExchange can be
    // used to assign the value. If the field is used
    // within the body of class Test, it must be cast to
    // type ClassA.
    private Object classAValue = null;
    // This property can be set once to an instance of 
    // ClassA. Attempts to set it again cause an
    // exception to be thrown.
    public ClassA ClassA {
        set {
            // CompareExchange compares the value in classAValue
            // to null. The new value assigned to the ClassA
            // property, which is in the special variable 'value',
            // is placed in classAValue only if classAValue is
            // equal to null.
            if (null != Interlocked.CompareExchange(ref classAValue,
                (Object) value, null)) {
                // CompareExchange returns the original value of 
                // classAValue; if it is not null, then a value 
                // was already assigned, and CompareExchange did not
                // replace the original value. Throw an exception to
                // indicate that an error occurred.
                throw new ApplicationException("ClassA was already set.");
            }
        }
        get {
            return (ClassA) classAValue;
        }
    }
}

For an example that uses Monitor and Interlocked in combination, see Monitor.

See Also

Threading | Threading Objects and Features | Interlocked Class | Monitor