Monitor.Pulse和PulseAll要求其操作上的锁在调用时锁定。 这一要求似乎是不必要的和有害的性能。 我的第一个想法是,这导致2间浪费上下文切换,但这是由以下nobugz(感谢)修正。 我仍然不确定是否涉及浪费的上下文切换一个潜在的 ,因为它在等待监视器上的其他线程(或多个)已经可用于sheduler,但如果他们被安排,他们将只能够运行一些指令击中互斥,并且具有前再次上下文切换。 这看起来更简单,更快,如果锁被调用Monitor.Pulse 之前解锁。
并行线程条件变量实现相同的概念,但它不具有上述限制:你可以调用调用pthread_cond_broadcast即使你不拥有互斥锁。 我认为这是一个证明的要求是没有道理的。
编辑 :我意识到,一个锁需要保护,通常是在Monitor.Pulse前更改了共享资源。 我想说这个锁可能已经对资源的访问之后,但在脉冲之前因为监视器会支持这个解锁。 这将有助于限制锁在此期间,共享资源访问的时间最短。 因此:
void f(Item i)
{
lock(somequeue) {
somequeue.add(i);
}
Monitor.Pulse(somequeue); // error
}
你的假设,即脉冲()调用调用线程切换是不正确的。 它只是移动从等待队列中的线程就绪队列。 退出()调用,使开关,这是第一次在就绪队列中的线程。
原因与记忆障碍和保证线程安全的事情。
所使用的共享变量(条件句)来确定是否一个脉冲()需要将所涉及的所有主题进行检查。 如果没有记忆障碍,变化可能保持在寄存器中,并从一个线程到另一个看不见的。 读取和写入也可以在线程查看时重新排序。
然而,从一个锁内访问变量使用一个内存屏障,因此它们对所有相关的线程访问。 锁内的所有操作似乎持有相同的锁的其他线程的角度来看原子执行。
另外,也没有需要多个上下文切换,如你推测。 等待的线程都放在一个(名义上FIFO)队列,而他们正在与脉冲()引发的,他们不是完全可运行,直到锁被放弃(再次,部分是由于内存屏障)。
有关这些问题的一个很好的讨论,请参见: http://www.albahari.com/threading/part4.aspx#_Wait_and_Pulse
我发现本文中的答案:
http://research.microsoft.com/pubs/64242/implementingcvs.pdf
它指出:
由于我们正在考虑这个级别的线程实现的,我要指出的最后一个性能问题,以及如何处理它。 如果信号被称为在持有锁的米,如果你在一个多处理器上运行,新线惊醒很可能立即开始运作。 这将导致它的几个指令后,在再次阻止(2)当它要拴M。 如果你想避免这些额外的重新安排,你需要安排的线程直接从条件变量队列转移到等待m个线程的队列。 这是在Java或C#,这都要求呼叫信号或者广播当m举行特别重要。
在全部的纸是有点模糊,不提许多实施细节,这是相当的伪/大专院校的水平。 但很显然它是谁写的家伙已经在实际.NET实现的责任。
但大致地说:信号仅仅是一个逻辑/用户级别的操作,并且不激发原始的象条件变量信号的时候了。 它不仅会等等锁定范围退出。 因此,有没有性能问题。 这是当一个用来操作条件变量直接的确忠实地不安。
等待被设计成与有条件的检查中使用。 如果条件检查未锁内完成,这将是可能发生的事件的顺序如下:
- 有条件的检查表明等待是必要的。
- 另一个线程改变了条件,使等待是没有必要的,然后做一个脉冲或PulseAll。
- 第一线程,在观察该等待是必要的,进行等待。
一旦事件该序列出现,这是完全可能的,任何事都不能再次脉冲锁定(除非出现这样的情况的等待将再次是必要的,并再次不再是必要的)。 因此,线#1可以等待永远的,永远不会到来的事件。
把条件检查和等待中的锁避免了这种危险,因为会有没有办法,另一个线程改变条件检查的时间和等待开始的时间之间的条件。 因此另一个线程更改了条件,并做了脉冲可以放心的是,第一个线程要么检查之后它被改变(从而避免了等待)的条件或者正在执行当脉冲执行(并且因而能够继续等待)。