C# 中的 Monitor.Pulse 显示为次优:必须在锁定范围内
本文关键字:范围内 锁定 Monitor 中的 Pulse 显示 | 更新日期: 2023-09-27 17:56:53
剧透注:问题是最后一句话。
在 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);
}
...
}
这是取自我代码的一个例子。如果我将脉冲放在锁定范围之外,它不会编译。但是,这是正确的方法:c.f:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms686903(v=vs.85).aspx和:http://www.installsetupconfig.com/win32programming/threadprocesssynchronizationapis11_7.html(搜索"内部")
事实上,当你仍然处于关键部分时发出睡眠线的信号是愚蠢的。因为睡眠线程无法唤醒(不是立即),因为它也在批评部分内!
因此,我希望 .NET 或 C# Pulse 调用实际上只是标记锁定对象,以便当它超出范围时,它实际上此时会"脉冲"条件变量。因为否则,它会产生最优性问题。
那么,为什么选择这样的监视器对象的设计呢?
编辑:
我在这篇文章中找到了答案:http://research.microsoft.com/pubs/64242/implementingcvs.pdf"优化信号和广播"部分以及上一节关于NT内核以及如何在信号量之上制作条件变量,这就是引入"的队列"的原因。现在,这使我成为一名更好的工程师。
事实上,当你仍然处于关键部分时发出睡眠线的信号是愚蠢的。因为睡眠线无法唤醒
Pulse
不希望线程运行;它只期望在 2 个队列(等待和就绪)之间移动线程。"不去做某事"是通过Exit
(或lock
结束)释放锁的一部分。实际上,这不是问题,因为Monitor.Pulse
通常发生在Wait
或Exit
之前。
因此,我希望 .NET 或 C# Pulse 调用实际上只是标记锁定对象,以便当它超出范围时,它实际上此时会"脉冲"条件变量。因为否则,它会产生最优性问题。
同样,这些是不同的问题:在等待和就绪之间移动是一回事;退出锁已经拥有实际激活下一个就绪线程的所有代码。
> 你不明白同步的基本问题。什么是"监视器",线程休眠是什么意思,它即将被唤醒意味着什么?
监视器是中级同步结构。这不是具有总线停止 XCHG 操作的低级小易失性布尔标志,也不是需要数十种其他特殊机制的高级线程池处理程序。
在显示器上,许多线程可能会休眠。那里有逻辑队列,即保持进入睡眠/唤醒的顺序,或保证适当的时间安排和公平的机制。我不会详细介绍,所有这些都在网络上,甚至在维基上。
除此之外,该操作是脉冲。脉冲是瞬时的。它不会"粘住"。脉搏会唤醒那些现在睡觉的人。如果在脉冲后另一个检查监视器,它将进入睡眠状态。
现在想象一下:你有一个由 5 个睡眠线程组成的队列。一个线程(第 6 个)现在想要脉冲它们,另一个线程(第 7 个)想要检查显示器。
第 6 和第 7 个并行运行,真正同时运行,因为您有四核 CPU。
那么,告诉我,如果第 6 个开始脉冲和唤醒并从队列中删除唤醒的线程,同时第 7 个开始将自己添加到队列中,队列的实现会发生什么?
为了解决这个问题,内部队列必须在内部同步和锁定,因此一次只有一个线程修改它们。
嗯等等。我们只是偶然发现了一个我们想要同步某些东西的情况,为了正确地做到这一点,我们需要同步另一件事?不好。
因此,在与显示器本身通信之前,实际的锁定是在外部完成的。这是为了实现单一锁定,而不是引入多层分层锁。
这样它更简单、更快速、更节省资源。