Interlocked

I metodi di Interlocked, CompareExchange, Decrement, Exchange e Increment, offrono un semplice meccanismo per sincronizzare l'accesso a una variabile condivisa da più thread. I thread di processi differenti possono utilizzare questo meccanismo se la variabile si trova nella memoria condivisa.

Le funzioni Increment e Decrement combinano le operazioni di incremento o decremento della variabile e di controllo del valore ottenuto. Questa operazione atomica risulta utile in un sistema operativo multitasking in cui l'esecuzione di un thread può essere interrotta per concedere una porzione di tempo del processore a un altro thread. Senza questo tipo di sincronizzazione, un thread potrebbe incrementare una variabile ma essere interrotto dal sistema prima di poterne verificare il risultato ottenuto. Un secondo thread potrebbe quindi aumentare la stessa variabile. Quando il primo thread riceve la successiva porzione di tempo, controlla il valore della variabile che è stata, a questo punto, aumentata non una volta ma due volte. Le funzioni di Interlocked di accesso alla variabile proteggono da questo tipo di errore.

La funzione Exchange scambia atomicamente i valori delle variabili specificate. La funzione CompareExchange combina due operazioni: il confronto di due valori e l'archiviazione di un terzo valore in una delle variabili, in base al risultato del confronto. È possibile utilizzare CompareExchange per proteggere i calcoli più complessi dei semplici incrementi e decrementi. Nell'esempio riportato di seguito viene illustrato un metodo thread-safe per aumentare un totale parziale.

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;
    }
}

Nei moderni processori i metodi della classe Interlocked possono essere spesso implementati da una singola istruzione. In questo modo i metodi della classe Interlocked forniscono una sincronizzazione dalle prestazioni molto elevate e possono essere utilizzati per generare meccanismi di sincronizzazione di livello più elevato, come gli spin lock.

Gli overload dei metodi Exchange e CompareExchange accettano argomenti di tipo Object. Il primo argomento di ciascuno di questi overload è ref Object (ByRef ... As Object in Visual Basic) e, per garantire l'indipendenza dai tipi, la variabile passata a questo argomento deve essere rigorosamente di tipo Object; non è sufficiente eseguire il cast del primo argomento sul tipo Object durante la chiamata di questi metodi. Nell'esempio seguente viene illustrata una proprietà di tipo ClassA che può essere impostata una sola volta.

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;
        }
    }
}

Per un esempio che utilizza Monitor e Interlocked in combinazione, vedere Monitor.

Vedere anche

Threading | Oggetti e funzionalità del threading | Classe Interlocked | Monitor