インタロックされた操作
更新 : 2007 年 11 月
Interlocked クラスは、複数のスレッドが共有する変数へのアクセスを同期化するメソッドを提供します。この変数が共有メモリにある場合、各プロセスのスレッドがこの機構を使用できます。インタロックされた操作はアトミックです。つまり、操作全体が 1 つの単位のため、同じ変数の別のインタロックされた操作によって中断されることはありません。これは、メモリ アドレスから値を読み込んだ後、変更して格納できるようになる前にスレッドを中断できるプリエンプティブ マルチスレッドのオペレーティング システムで重要です。
Interlocked クラスには、次の操作があります。
.NET Framework Version 2.0 では、Add メソッドは変数に整数値を追加して変数の新しい値を返します。
.NET Framework Version 2.0 では、Read メソッドはアトミック操作として 64 ビットの整数値を読み込みます。これは、64 ビットの整数の読み込みが、通常、アトミック操作ではない 32 ビットのオペレーティング システムで有効です。
Increment メソッドと Decrement メソッドは、変数をインクリメントまたはデクリメントした値を返します。
Exchange メソッドは、指定された変数で値をアトミックに交換して値を返し、新しい値で置き換えます。.NET Framework Version 2.0 では、任意の参照型の変数に対してこのメソッドのジェネリック オーバーロードを使用してこの交換を実行できます。「Exchange<T>(T%, T)」を参照してください。
CompareExchange メソッドも 2 つの値を交換しますが、比較の結果しだいです。.NET Framework Version 2.0 では、任意の参照型の変数に対してこのメソッドのジェネリック オーバーロードを使用してこの交換を実行できます。「CompareExchange<T>(T%, T, T)」を参照してください。
今日のプロセッサでは、多くの場合、Interlocked クラスのメソッドは単一の命令で実装できます。このように、高性能の同期機能を提供するこれらのメソッドによって、スピン ロックなどのより高水準の同期機構を構築できます。
Monitor クラスと Interlocked クラスを組み合わせて使用する例については、「Monitor」を参照してください。
CompareExchange の例
CompareExchange メソッドは、単純なインクリメントまたはデクリメントよりも複雑な計算を保護するために使用できます。浮動小数点値として格納されている現在の合計に対して加算を行うスレッド セーフ メソッドの例を次に示します。整数に対しては、Add メソッドを使用する方法が簡単です。完全なコード例については、単精度と倍精度の浮動小数点引数 (CompareExchange(Single%, Single, Single) と CompareExchange(Double%, Double, Double)) を受け取る 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 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;
}
}
Exchange と CompareExchange の型指定されていないオーバーロード
Exchange メソッドと CompareExchange メソッドには、Object 型の引数を受け取るオーバーロードがあります。各オーバーロードの第 1 引数は ref Object (Visual Basic では ByRef … As Object) で、この引数に渡す変数の型は Object 型である必要があります。このメソッドを呼び出すときに、第 1 引数を Object 型にキャストするだけでは不十分です。
メモ : |
---|
.NET Framework Version 2.0 では、Exchange メソッドと CompareExchange メソッドのジェネリック オーバーロードを使用して厳密に型指定された変数を交換します。 |
一度だけ設定できる ClassA 型のプロパティのコード例を次に示します。このプロパティは、.NET Framework Version 1.0 または 1.1 で実装できます。
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;
}
}
}