Delen via


Interlocked Operations

The Interlocked class provides methods that synchronize 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. Interlocked operations are atomic — that is, the entire operation is a unit that cannot be interrupted by another interlocked operation on the same variable. This is important in operating systems with preemptive multithreading, where a thread can be suspended after loading a value from a memory address, but before having the chance to alter it and store it.

The Interlocked class provides the following operations:

  • In the .NET Framework version 2.0, the Add method adds an integer value to a variable and returns the new value of the variable.

  • In the .NET Framework version 2.0, the Read method reads a 64-bit integer value as an atomic operation. This is useful on 32-bit operating systems, where reading a 64-bit integer is not ordinarily an atomic operation.

  • The Increment and Decrement methods increment or decrement a variable and return the resulting value.

  • The Exchange method performs an atomic exchange of the value in a specified variable, returning that value and replacing it with a new value. In the .NET Framework version 2.0, a generic overload of this method can be used to perform this exchange on a variable of any reference type. See Exchange<T>(T, T).

  • The CompareExchange method also exchanges two values, but contingent on the result of a comparison. In the .NET Framework version 2.0, a generic overload of this method can be used to perform this exchange on a variable of any reference type. See CompareExchange<T>(T, T, T).

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

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

CompareExchange Example

The CompareExchange method 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 stored as a floating point number. (For integers, the Add method is a simpler solution.) For complete code examples, see the overloads of CompareExchange that take single-precision and double-precision floating-point arguments (CompareExchange(Single, Single, Single) and CompareExchange(Double, Double, Double)).

Imports System
Imports 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 totalValue As Double = 0.0

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

    ' AddToTotal safely adds a value to the running total.
    Public Function AddToTotal(addend As Double) As Double
        Dim initialValue, computedValue As Double
        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
using System;
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 double totalValue = 0;

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

    // AddToTotal safely adds a value to the running total.
    public double AddToTotal(double addend)
    {
        double 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;
    }
}
using namespace System;
using namespace System::Threading;

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

public:
    // The Total property returns the running total.
    property double Total
    {
        double get() { return totalValue; }
    }

    // AddToTotal safely adds a value to the running total.
    double AddToTotal(double addend)
    {
        double 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(
            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;
    }
};

Untyped Overloads of Exchange and CompareExchange

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.

Note

In the .NET Framework version 2.0, use the generic overloads of the Exchange and CompareExchange methods to exchange strongly typed variables.

The following code example shows a property of type ClassA that can be set only once, as it might be implemented in the .NET Framework version 1.0 or 1.1.

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 classAValue As Object = Nothing
    ' This property can be set once to an instance of
    ' ClassA. Attempts to set it again cause an
    ' exception to be thrown.
    Public Property ClassA() As ClassA
        Get
            Return CType(classAValue, ClassA)
        End Get

        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 Not (Nothing Is Interlocked.CompareExchange(classAValue, _
                CType(value, [Object]), Nothing)) Then
                ' 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.")
            End If
        End Set
    End Property
End Class
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
    {
        get
        {
            return (ClassA) classAValue;
        }

        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.");
            }
        }
    }
}
public ref 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:
    static Object^ classAValue = nullptr;

    // This property can be set once to an instance of
    // ClassA. Attempts to set it again cause an
    // exception to be thrown.
public:
    property ClassA^ classA
    {
        ClassA^ get()
        {
            return (ClassA^) classAValue;
        }

        void set(ClassA^ value)
        {
            // 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 (nullptr != Interlocked::CompareExchange(classAValue,
                (Object^) value, nullptr))
            {
                // 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 gcnew ApplicationException("ClassA was already set.");
            }
        }
    }
};

See Also

Reference

Interlocked

Monitor

Other Resources

Managed Threading

Threading Objects and Features