Opérations verrouillées

Mise à jour : novembre 2007

La classe Interlocked fournit des méthodes qui synchronisent l'accès à une variable qui est partagée par plusieurs threads. Les threads de processus différents peuvent utiliser ce mécanisme si la variable est en mémoire partagée. Les opérations verrouillées sont atomiques - c'est-à-dire que l'opération entière est une unité qui ne peut pas être interrompue par une opération verrouillée sur la même variable. Cela est important dans les systèmes d'exploitation avec un multithread préemptif, où un thread peut être suspendu après le chargement d'une valeur à partir d'une adresse mémoire, mais avant d'avoir l'opportunité de la modifier et de la stocker.

La classe Interlocked fournit les opérations suivantes :

  • Dans le .NET Framework version 2.0, la méthode Add ajoute une valeur entière à une variable et retourne la nouvelle valeur de la variable.

  • Dans le .NET Framework version 2.0, la méthode Read lit une valeur entière de 64 bits comme une opération atomique. Cela utile sur les systèmes d'exploitation de 32 bits, où la lecture d'un entier de 64 bits n'est d'ordinaire pas une opération atomique.

  • Les méthodes Increment et Decrement incrémentent ou décrémentent une variable et retourne la valeur résultante.

  • La méthode Exchange exécute un échange atomique de la valeur dans une variable spécifiée, en retournant cette valeur et la remplaçant par une nouvelle valeur. Dans le .NET Framework version 2.0, une surcharge générique de cette méthode peut être utilisée pour exécuter cet échange sur une variable de tout type référence. Consultez Exchange<T>(T%, T).

  • La méthode CompareExchange échange également deux valeurs, mais dépend du résultat d'une comparaison. Dans le .NET Framework version 2.0, une surcharge générique de cette méthode peut être utilisée pour exécuter cet échange sur une variable de tout type référence. Consultez CompareExchange<T>(T%, T, T).

Sur les processeurs modernes, les méthodes de la classe Interlocked peuvent souvent être implémentées par une seule instruction. Ainsi, elles fournissent d'excellentes performances de synchronisation et peuvent s'utiliser pour générer des mécanismes de synchronisation de niveau plus élevé comme les spin locks.

Pour obtenir un exemple qui utilise les classes Monitor et Interlocked dans la combinaison, consultez Moniteurs.

Exemple CompareExchange

La méthode CompareExchange peut être utilisée pour protéger des calculs qui sont plus compliqués que de simples incréments et décréments. L'exemple suivant montre une méthode thread-safe qui ajoute à un total cumulé stocké comme un nombre à virgule flottante. (Pour les entiers, la méthode Add est une solution plus simple.) Pour les exemples de code complets, consultez les surcharges de CompareExchange qui acceptent des arguments de nombre à virgule flottante simple précision et double précision (CompareExchange(Single%, Single, Single) et CompareExchange(Double%, Double, Double)).

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 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(ByVal 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.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;
    }
}

Surcharges non typées d'Exchange et de CompareExchange

Les méthodes Exchange et CompareExchange ont des surcharges qui acceptent des arguments de type Object. Le premier argument de chacune de ces surcharges est ref Object (ByRef … As Object en Visual Basic), et la sécurité de type requiert la variable passée à cet argument à taper strictement comme Object ; vous ne pouvez pas simplement effectuer de cast sur le premier argument à taper Object lors de l'appel de ces méthodes.

Remarque :

Dans le .NET Framework version 2.0, utilisez les surcharges génériques des méthodes Exchange et CompareExchange pour échanger des variables fortement typées.

L'exemple de code suivant affiche une propriété de type ClassA qui peut être définie uniquement une fois, comme elle peut être implémentée dans la version 1.0 ou 1.1 du .NET Framework.

Public Class ClassB
    ' The private field that stores the value for the
    ' ClassA property is intialized to Nothing. 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
        Set
            ' CompareExchange compares the value in classAValue
            ' to Nothing. 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 Nothing.
            If Not (Nothing Is Interlocked.CompareExchange(classAValue, _
                    CType(value, [Object]), Nothing)) Then
                ' CompareExchange returns the original value of 
                ' classAValue; if it was not Nothing, 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
        Get
            Return CType(classAValue, ClassA)
        End Get
    End Property
End Class ' ClassB
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;
        }
    }
}

Voir aussi

Référence

Interlocked

Monitor

Autres ressources

Threading managé

Fonctionnalités et objets de threading