互锁操作
更新:2007 年 11 月
Interlocked 类提供这样一些方法,即同步对多个线程共享的变量的访问的方法。如果该变量位于共享内存中,则不同进程的线程就可以使用该机制。互锁操作是原子的,即整个操作是不能由相同变量上的另一个互锁操作所中断的单元。这在抢先多线程操作系统中是很重要的,在这样的操作系统中,线程可以在从某个内存地址加载值之后但是在有机会更改和存储该值之前被挂起。
Interlocked 类提供了以下操作:
在 .NET Framework 2.0 版中,Add 方法向变量添加一个整数值并返回该变量的新值。
在 .NET Framework 2.0 版中,Read 方法作为一个原子操作读取一个 64 位整数值。这在 32 位操作系统上是有用的,在 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 方法可用来保护比简单的递增和递减更为复杂的计算。下面的示例演示一个线程安全的方法,该方法向存储为浮点数的累计值执行加运算。(对于整数,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 类型的参数的重载。这其中每个重载的第一个参数都是 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;
}
}
}