Condividi tramite


Operazioni interlocked

La classe Interlocked fornisce metodi per la sincronizzazione dell'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 operazioni interlocked sono atomiche. In altri termini, un'intera operazione costituisce un'unità che non può essere interrotta da un'altra operazione interlocked sulla stessa variabile. Questa caratteristica è importante nei sistemi operativi con multithreading preemptive, in cui un thread può essere sospeso dopo il caricamento di un valore da un indirizzo di memoria, ma prima che sia possibile modificarlo e memorizzarlo.

La classe Interlocked fornisce le seguenti operazioni:

  • In .NET Framework versione 2.0 il metodo Add aggiunge un valore integer a una variabile e restituisce il nuovo valore della variabile.

  • In .NET Framework versione 2.0 il metodo Read legge un valore integer a 64 bit come un'operazione atomica. Questa caratteristica è utile sui sistemi a 32 bit, in cui la lettura di un intero a 64 bit non è normalmente un'operazione atomica.

  • I metodi Increment e Decrement incrementano o decrementano una variabile e restituiscono il valore risultante.

  • Il metodo Exchange esegue uno scambio atomico del valore in una variabile specificata, restituendo tale valore e sostituendolo con uno nuovo. In .NET Framework versione 2.0 è possibile utilizzare un overload generico di questo metodo per eseguire questo scambio su una variabile di qualsiasi tipo di riferimento. Vedere Exchange<T>(T, T).

  • Anche il metodo CompareExchange scambia due valori, ma in base al risultato di un confronto. In .NET Framework versione 2.0 è possibile utilizzare un overload generico di questo metodo per eseguire questo scambio su una variabile di qualsiasi tipo di riferimento. Vedere CompareExchange<T>(T, T, T).

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

Per un esempio di utilizzo combinato delle classi Monitor e Interlocked, vedere Monitor.

Esempio di metodo CompareExchange

Il metodo CompareExchange può essere utilizzato per la protezione di calcoli più complessi rispetto al semplice incremento o decremento. Nell'esempio riportato di seguito viene illustrato un metodo thread-safe per ottenere un totale parziale memorizzato come numero a virgola mobile. Per gli interi, il metodo Add costituisce una soluzione più semplice. Per esempi di codice completi, vedere gli overload di CompareExchange che accettano argomenti a virgola mobile, con precisione singola e precisione doppia (CompareExchange(Single, Single, Single) e 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;
    }
};

Overload non tipizzati di Exchange e CompareExchange

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 è possibile eseguire semplicemente il cast del primo argomento nel tipo Object quando vengono chiamati questi metodi.

NotaNota

In .NET Framework versione 2.0 utilizzare gli overload generici dei metodi Exchange e CompareExchange per scambiare variabili fortemente tipizzate.

Nell'esempio di codice riportato di seguito viene illustrata una proprietà di tipo ClassA che può essere utilizzata una sola volta poiché può essere implementata in .NET Framework versione 1.0 o 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.");
            }
        }
    }
};

Vedere anche

Riferimenti

Interlocked

Monitor

Altre risorse

Threading gestito

Oggetti e funzionalità del threading