C# Volatile字段:如何实际获取字段的最新写入值?
考虑以下例子:C# Volatile字段:如何实际获取字段的最新写入值?,c#,.net,multithreading,.net-4.5,volatile,C#,.net,Multithreading,.net 4.5,Volatile,考虑以下例子: private int sharedState = 0; private void FirstThread() { Volatile.Write(ref sharedState, 1); } private void SecondThread() { int sharedStateSnapshot = Volatile.Read(ref sharedState); Console.WriteLine(sharedStateSnapshot); } 直
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
然而,我现在的理解是:
- Volatile.Write()发出释放栅栏。这意味着在将1分配给
sharedState
之后,不会发生先前的加载或存储(按程序顺序)
- Volatile.Read()发出一个获取围栏。这意味着在将
sharedState
复制到sharedStateSnapshot
之前,不会发生后续加载或存储(按程序顺序)
或者,换一种说法:
- 当
sharedState
实际发布到所有处理器内核时,写入之前的所有内容也将被发布,并且
- 获取地址
sharedStateSnapshot
中的值时<代码>共享数据状态
必须已获取
因此,如果我的理解是正确的,那么如果写入的FirstThread()
尚未释放,那么就没有任何东西可以阻止获取sharedState
变得“过时”
如果这是真的,我们如何实际确保(假设最脆弱的处理器内存模型,如ARM或Alpha)程序始终打印1?(或者我的心智模型在什么地方出错了?你是对的,不能保证所有处理器都能立即看到发布存储<代码>易失性。读和
易失性。写
提供获取/释放语义,但没有即时性保证
不过,volatile
修饰符似乎可以做到这一点。编译器将发出一条操作码.Volatile
IL指令,抖动将告诉处理器不要将变量存储在其任何寄存器上(请参阅)
但是,你为什么需要它是立即的呢?如果您的
SecondThread
在实际写入值之前提前几毫秒运行,该怎么办?由于日程安排是不确定的,您的程序的正确性无论如何不应该依赖于这种“即时性”。您的理解是正确的,您确实无法确保程序始终使用这些技术打印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);
}
我想引用:
坦率地说,我不鼓励你做一个不稳定的领域。易变字段表明您正在做一些完全疯狂的事情:您试图在两个不同的线程上读取和写入相同的值,而没有设置锁
这同样适用于调用Volatile.Read
和Volatile.Write
。事实上,它们甚至比volatile字段更糟糕,因为它们需要您手动执行volatile
修饰符自动执行的操作
直到最近,我的印象是,只要
FirstThread()确实是在SecondThread()之前执行的,这个程序
无法输出除1以外的任何内容
当你继续解释自己时,这种印象是错误的Volatile.Read
只是在其目标上发出一个读取操作,后跟一个内存屏障;内存屏障阻止在执行当前线程的处理器上重新排序操作,但这在这里没有帮助,因为
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。这取决于您如何知道
FirstThread()
在SecondThread()之前运行。这一知识是否也基于其他代码和内存模型,或者这正是程序员认为的情况?@Damien_不相信者我不确定我是否理解你的问题……你说“FirstThread()
确实在SecondThread()
之前执行过”—但你怎么知道?您是否有其他一些代码没有向我们展示,它们在某种程度上也依赖于内存模型,能够检测到FirstThread
具有def