考虑以下示例:
private int sharedState = 0;
private void FirstThread() {
Volatile.Write(ref sharedState, 1);
}
private void SecondThread() {
int sharedStateSnapshot = Volatile.Read(ref sharedState);
Console.WriteLine(sharedStateSnapshot);
}
直到最近,我的印象是,只要
FirstThread()
确实在 SecondThread()
之前执行,这个程序就只能输出 1 。但是,我现在的理解是:
sharedState
之后,可能不会发生先前的加载或存储(按程序顺序)。 sharedState
复制到 sharedStateSnapshot
之前不会发生后续加载或存储(按程序顺序)。 或者,换一种说法:
sharedState
真正释放到所有处理器内核时,写入之前的所有内容也将被释放,并且,sharedStateSnapshot
中的值时; sharedState
必须已经被获取。 如果我的理解因此是正确的,那么如果
sharedState
中的写入尚未发布,则没有什么可以阻止 FirstThread()
的获取“过时”。如果这是真的,我们如何才能真正确保(假设最弱的处理器内存模型,例如 ARM 或 Alpha),程序将始终打印 1 ? (或者我在我的心智模型中的某个地方犯了错误?)
最佳答案
您的理解是正确的,并且您确实无法确保程序始终使用这些技术打印 1。为了确保您的程序将打印 1,假设线程 2 在线程 1 之后运行,您需要在每个线程上设置两个栅栏。
最简单的方法是使用 lock
关键字:
private int sharedState = 0;
private readonly object locker = new object();
private void FirstThread()
{
lock (locker)
{
sharedState = 1;
}
}
private void SecondThread()
{
int sharedStateSnapshot;
lock (locker)
{
sharedStateSnapshot = sharedState;
}
Console.WriteLine(sharedStateSnapshot);
}
我想引用 Eric Lippert :
这同样适用于调用
Volatile.Read
和 Volatile.Write
。事实上,它们甚至比 volatile 字段更糟糕,因为它们需要您手动执行 volatile
修饰符自动执行的操作。