次の方法で共有


Interlocked

Interlocked メソッド CompareExchangeDecrementExchange、および Increment には、複数のスレッドで共有する変数へのアクセスを同期するための、単純な機構が用意されています。この変数が共有メモリにある場合、各プロセスのスレッドがこの機構を使用できます。

Increment 関数と Decrement 関数は、変数値の増減操作と結果値の確認操作を組み合わせます。この分割不可能な操作は、あるスレッドの実行を中断し、プロセッサのタイム スライスを別のスレッドに与えることのできるマルチタスク オペレーティング システムで使用すると便利です。この同期機能がない場合、変数値をインクリメントしたものの、その結果値を確認する前にスレッド自身がシステムによって中断されることがあります。その後、次のスレッドは同じ変数の値をインクリメントできます。この場合、最初のスレッドが次のタイム スライスを受け取り、変数の値を確認した時点で、変数の値は 1 回ではなく 2 回インクリメントされています。Interlocked 変数アクセス関数によって、このようなエラーを防ぐことができます。

Exchange 関数は、指定された変数の値を自動的に交換します。CompareExchange 関数は、2 つの値を比較する操作と、その比較結果に基づいて 3 番目の値を変数の 1 つに格納する操作を結合します。CompareExchange は、単純なインクリメントまたはデクリメントよりも複雑な計算を保護するために使用できます。現在の合計に対して加算を行うスレッド セーフ メソッドの例を次に示します。

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

今日のプロセッサでは、多くの場合、Interlocked クラスのメソッドは単一の命令で実装できます。このように、Interlocked クラスのメソッドにはパフォーマンスの高い同期が用意されており、これらのメソッドを使用すると、スピン ロックなどのより高水準の同期機構を構築できます。

Exchange メソッドと CompareExchange メソッドには、Object 型の引数を持つオーバーロードがあります。オーバーロードの第 1 引数は ref Object (Visual Basic では ByRef ... As Object) であり、この引数に渡す変数の型は Object 型である必要があります。このメソッドを呼び出すときに、第 1 引数を Object 型にキャストするだけでは不十分です。1 回だけ設定できるタイプ ClassA のプロパティを表示する例を次に示します。

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

MonitorInterlocked を組み合わせて使用する例については、「Monitor」を参照してください。

参照

スレッド処理 | スレッド処理オブジェクトと機能 | Interlocked クラス | Monitor クラス