Interlocked 作業
更新:2007 年 11 月
Interlocked 類別提供方法讓您可以同步處理對多個執行緒所共用之變數的存取。如果變數存放在共用記憶體上,則不同處理序的執行緒可使用這個辦法。Interlocked 作業屬於原子式,也就是說,整個作業是一個單位,相同變數的其他 Interlocked 作業無法將它中斷。這一點對於具有先佔式多執行緒處理功能的作業系統而言很重要,在這種作業系統中,從記憶體位址載入某個值之後,並在有機會變更及儲存它之前,您可以暫止執行緒。
Interlocked 類別提供了下列作業:
在 .NET Framework 2.0 版中,Add 方法會將某個整數值加入至變數,並傳回該變數的新值
在 .NET Framework 2.0 版中,Read 方法會以不可部分完成的作業 (Atomic Operation) 之方式來讀取 64 位元整數值,這在 32 位元的作業系統上很有用,在這種作業系統上,通常不是以不可部分完成的作業之方式來讀取 64 位元整數
Exchange 方法會在指定的變數中執行不可部分完成的數值交換,傳回該值並以新的值取代它。在 .NET Framework 2.0 版中,這個方法的泛型多載可以用來在任何參考型別的變數上執行這項交換。請參閱 Exchange<T>(T%, T)。
CompareExchange 方法也會交換兩個值,但要視比較結果而定。在 .NET Framework 2.0 版中,這個方法的泛型多載可以用來在任何參考型別的變數上執行這項交換。請參閱 CompareExchange<T>(T%, T, T)。
在現代的處理器上,通常可利用單一指令來實作 Interlocked 類別的方法。因此,它們提供非常高效能的同步處理,並且可用來建置高階同步處理辦法,如微調鎖定。
如需結合使用 Monitor 和 Interlocked 類別的範例,請參閱監視器。
CompareExchange 範例
CompareExchange 方法可以用來保護比簡單加減更複雜的計算。下列範例示範加入至儲存為浮點數之累加值中的安全執行緒 (Thread-Safe) 方法 (如果是整數,Add 方法會是比較簡單的方案)。如需完整的程式碼範例,請參閱 CompareExchange 採用單精度和雙精度浮點引數的多載 (CompareExchange(Single%, Single, Single) 和 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;
}
}
Exchange 和 CompareExchange 的不具型別多載
Exchange 和 CompareExchange 方法具有採用 Object 型別引數的多載。每個這種多載的第一個引數都是 ref Object (在 Visual Basic 中,是 ByRef … As Object),而型別安全則要求傳遞給這個引數的變數必須如同 Object 一般地型別嚴格;呼叫這些方法時,您無法只將第一個引數轉換成型別 Object。
注意事項: |
---|
在 .NET Framework 2.0 版中,請使用 Exchange 和 CompareExchange 方法的泛型多載來交換強型別變數。 |
下列程式碼範例將示範 ClassA 型別只能設定一次的屬性,.NET Framework 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;
}
}
}