C#中的Monitor.Pulse看起来不是最理想的:必须处于锁定范围内

时间:2013-02-01 09:57:15

标签: c# performance locking monitor condition-variable

扰流说明:问题是最后一句话。

在C#中,使用条件变量的经典模式如下:

lock (answersQueue)
{
    answersQueue.Enqueue(c);
    Monitor.Pulse(answersQueue); // condition variable "notify one".
}

和其他一些帖子:

lock (answersQueue)
{
    while (answersQueue.Count == 0)
    {
        // unlock answer queue and sleeps here until notified.
        Monitor.Wait(answersQueue);
    }
    ...
}

这是我的代码中的一个例子。 如果我将Pulse放在锁定范围之外,它就不会编译。 但是,这是正确的方法: c.f:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms686903(v=vs.85).aspx 和: http://www.installsetupconfig.com/win32programming/threadprocesssynchronizationapis11_7.html (搜索“内部”)

当你还处于关键部分时,确实发出睡眠信号是很愚蠢的。因为睡眠线程不能唤醒(不是立即),因为它也是INSIDE的一个批评部分!

因此,我希望.NET或C#Pulse调用实际上只是标记锁定对象,因此当它超出范围时,它实际上会在此时“脉冲”条件变量。否则,它将具有最优性问题。

那么为什么选择Monitor对象的设计呢?

编辑:

我在本文中找到了答案: http://research.microsoft.com/pubs/64242/implementingcvs.pdf “优化信号和广播”部分和前一节关于NT内核以及如何在信号量之上创建条件变量,这是引入“darned队列”的原因。 现在,这使我成为一名更好的工程师。

2 个答案:

答案 0 :(得分:1)

  

当你还处于关键部分时,确实发出睡眠信号是很愚蠢的。因为睡眠线程无法唤醒

Pulse 不希望让线程运行;它只希望在2个队列之间移动一个线程(等待和准备好)。 “不去做某事”是通过Exit(或lock的结尾)释放锁定的一部分。实际上,这不是问题,因为Monitor.Pulse通常恰好在WaitExit之前发生。

  

因此,我希望.NET或C#Pulse调用实际上只是标记锁定对象,因此当它超出范围时,它实际上会在此时“脉冲”条件变量。否则,它将具有最优性问题。

再次;这些是不同的问题:在等待和准备之间移动是一回事;退出锁已经具有实际激活下一个就绪线程的所有代码。

答案 1 :(得分:1)

您不了解同步的基本问题。什么是'监视器',线程睡眠是什么意思,什么意味着它将被唤醒?

监视器是中级同步结构。这不是一个带有总线暂停XCHG操作的低级小型易失性布尔标志,这不是需要许多其他特殊机制的高级线程池处理程序。

在显示器上,很多线程可能会睡眠。那里有逻辑队列,即保持顺序被置于睡眠/唤醒状态,或保证适当的时间安排和公平的机制。我不会详细介绍,所有内容都在网上,甚至在维基上。

除此之外,该操作是PULSE。脉冲是不稳定的。它不会“坚持”。 Pulse会唤醒那些正在睡觉的人。如果在脉冲之后另一个检查显示器,它将进入睡眠状态。

现在想象一下:你有一个5个睡眠线程的队列。一个线程(第6个)现在想要激活它们,而另一个线程(第7个)想要检查监视器。

由于你有四核CPU,所以第6和第7个并行,真正同时运行。

那么,告诉我,如果第6个开始脉冲和唤醒并从队列中删除唤醒线程,队列的实现会发生什么,同时第7个开始在那里添加自己?

要解决这个问题,内部队列必须在内部同步并锁定,因此每次只有一个线程会修改它们。

嗯等等。我们偶然发现了一个我们想要SYNCHRONIZE的东西,为了正确地做到这一点,我们需要在另一件事上同步SYNCHRONIZE?不好。

因此,在与显示器本身通信之前,实际的LOCK是在外部完成的。这是为了实现单一锁定,而不是引入多层分层锁。

这样,它更简单,更快速,更加资源友好。