考虑以下示例:
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()
过时' / p>
如果这是真的,我们如何才能确保(假设最弱的处理器内存模型,如ARM或Alpha),程序将始终打印 1 ? (或者我在某个地方的心理模型中犯了错误?)
答案 0 :(得分:6)
您的理解是正确的,确实无法确保程序始终使用这些技术打印1。为了确保程序打印1,假设线程2在第一个线程之后运行,则每个线程需要两个栅栏。
实现这一目标的最简单方法是使用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
修饰符自动执行的操作。
答案 1 :(得分:4)
您是对的,并不能保证所有处理器都能立即看到发布商店。 Volatile.Read
和Volatile.Write
为您提供获取/释放语义,但没有即时保证。
volatile
修饰符似乎可以做到这一点。编译器将发出OpCodes.Volatile
IL指令,抖动将告诉处理器不要将变量存储在任何寄存器上(参见Hans Passant's answer)。
但是为什么你还需要它立即?如果您的SecondThread
恰好在实际写入值之前的几毫秒内运行了怎么办?由于时间安排是非确定性的,因此您的计划的正确性不应该取决于这个"即时性"反正。
答案 2 :(得分:2)
直到最近,我的印象是,只要 FirstThread()确实在SecondThread()之前执行了这个程序 不能输出任何东西,只有1。
当你继续解释自己时,这种印象是错误的。 Volatile.Read
只是对其目标发出读取操作,然后是内存屏障;内存屏障阻止了执行当前线程的处理器上的操作重新排序,但这在这里没有用,因为
如果我的理解是正确的,那么没有什么可以做的 如果写入,则阻止获取sharedState为“陈旧” FirstThread()尚未发布。
这是正确的。从本质上讲,您使用的工具旨在帮助弱内存模型解决由竞争条件引起的可能问题。该工具无法帮助您,因为它不是它的功能。
如果这是真的,我们怎样才能真正确保(假设最弱) 程序将使用的处理器内存模型,如ARM或Alpha) 总是打印1? (或者我在心理模型中犯了错误 某处?)
再次强调:内存模型不是问题所在。为确保您的程序始终打印1,您需要做两件事:
SecondThread
可以对FirstThread
用来表示已完成的标志使用自旋锁。SecondThread
不会读取陈旧值。您可以通过将sharedState
标记为volatile
来轻松完成此操作 - 虽然此关键字当之无愧,但它是专门为此类用例设计的。所以在最简单的情况下,你可以举例:
private volatile int sharedState = 0;
private volatile bool spinLock = false;
private void FirstThread()
{
sharedState = 1;
// ensure lock is released after the shared state write!
Volatile.Write(ref spinLock, true);
}
private void SecondThread()
{
SpinWait.SpinUntil(() => spinLock);
Console.WriteLine(sharedState);
}
假设没有其他两个字段的写入,则保证此程序只输出1。