Monitor.Wait确保重新读取字段吗?
人们普遍接受(我相信!)一个lock
会强制重新加载字段中的任何值(基本上充当内存屏障或栅栏 – 我在这方面的术语有点松散,我害怕),结果是,只能在lock
内访问的字段本身不需要是volatile
。
(如果我错了,就说!)
这里提出了一个很好的评论,质疑如果代码执行Wait()
是否同样如此 – 即一旦它是Pulse()
d,它将从内存重新加载字段,还是它们可能在寄存器(等)中。
或者更简单:字段是否需要是volatile
以确保在Wait()
之后恢复时获得当前值?
看着reflection器, Wait
调用ObjWait
,这是一个managed internalcall
调用managed internalcall
(与Enter
相同)。
有问题的情景是:
bool closing; public bool TryDequeue(out T value) { lock (queue) { // arbitrary lock-object (a private readonly ref-type) while (queue.Count == 0) { if (closing) { // <==== (2) access field here value = default(T); return false; } Monitor.Wait(queue); // <==== (1) waits here } ...blah do something with the head of the queue } }
显然我可以让它变得volatile
,或者我可以把它移出来让我每次脉冲时退出并重新进入Monitor
,但我很想知道是否有必要 。
由于Wait()
方法正在释放并重新获取Monitor
锁,因此如果lock
执行内存fence语义,那么Monitor.Wait()
也将如此。
希望能够解决您的意见:
Monitor.Wait()
的锁定行为在docs( http://msdn.microsoft.com/en-us/library/aa332339.aspx )中,重点补充:
当一个线程调用Wait时,它会释放对象的锁定并进入对象的等待队列。 对象的就绪队列中的下一个线程(如果有)获取锁并且独占使用该对象。 所有调用
Wait
线程都会保留在等待队列中,直到它们收到来自PulseAll
的所有者发送的Pulse或PulseAll
信号。 如果发送Pulse
,则只有等待队列头部的线程受到影响。 如果发送PulseAll
,则所有等待该对象的线程都会受到影响。 当接收到信号时,一个或多个线程离开等待队列并进入就绪队列。 准备队列中的线程被允许重新获取锁。当调用线程重新获取对象上的锁时,此方法返回 。
如果您询问有关lock
/获取的Monitor
是否意味着内存屏障的参考, ECMA CLI规范将说明以下内容:
12.6.5锁和线程:
获取锁(
System.Threading.Monitor.Enter
或输入同步方法)应隐式执行易失性读操作,释放锁(System.Threading.Monitor.Exit
或保留同步方法)将隐式执行易失性写操作。 见§12.6.7。
12.6.7易失性读写:
易失性读取具有“获取语义”,这意味着保证在对CIL指令序列中的读取指令之后发生的对存储器的任何引用之前发生读取。 易失性写入具有“释放语义”,这意味着写入保证在CIL指令序列中的写入指令之前的任何存储器引用之后发生。
此外,这些博客条目还有一些可能感兴趣的细节:
继迈克尔·伯尔的回答之后, Wait
不仅释放并重新获得锁定,而且还这样做,以便另一个线程可以取出锁定以检查共享状态并调用Pulse
。 如果第二个线程没有取出锁定,那么Pulse
将抛出。 如果他们不Pulse
第一个线程的Wait
将不会返回。 因此,任何其他线程对共享状态的访问必须在适当的内存条件下发生。
因此,假设根据本地可检查的规则使用Monitor
方法,则所有内存访问都发生在锁内,因此只有锁的自动内存屏障支持是相关/必要的。
也许这次我可以帮助你…而不是使用volatile
你可以使用Interlocked.Exchange
和一个整数。
if (closing==1) { // <==== (2) access field here value = default(T); return false; } // somewhere else in your code: Interlocked.Exchange(ref closing, 1);
Interlocked.Exchange
是一种同步机制, volatile
不是......我希望这是值得的(但你可能已经考虑过了)。