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内核以及如何在信号量之上制作条件变量,这就是引入"的队列"的原因。现在,这使我成为一名更好的工程师。

C# 中的 Monitor.Pulse 显示为次优:必须在锁定范围内

事实上,当你仍然处于关键部分时发出睡眠线的信号是愚蠢的。因为睡眠线无法唤醒

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

因此,我希望 .NET 或 C# Pulse 调用实际上只是标记锁定对象,以便当它超出范围时,它实际上此时会"脉冲"条件变量。因为否则,它会产生最优性问题。

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

> 你不明白同步的基本问题。什么是"监视器",线程休眠是什么意思,它即将被唤醒意味着什么?

监视器是中级同步结构。这不是具有总线停止 XCHG 操作的低级小易失性布尔标志,也不是需要数十种其他特殊机制的高级线程池处理程序。

在显示器上,许多线程可能会休眠。那里有逻辑队列,即保持进入睡眠/唤醒的顺序,或保证适当的时间安排和公平的机制。我不会详细介绍,所有这些都在网络上,甚至在维基上。

除此之外,该操作是脉冲。脉冲是瞬时的。它不会"粘住"。脉搏会唤醒那些现在睡觉的人。如果在脉冲后另一个检查监视器,它将进入睡眠状态。

现在想象一下:你有一个由 5 个睡眠线程组成的队列。一个线程(第 6 个)现在想要脉冲它们,另一个线程(第 7 个)想要检查显示器。

第 6 和第 7 个并行运行,真正同时运行,因为您有四核 CPU。

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

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

嗯等等。我们只是偶然发现了一个我们想要同步某些东西的情况,为了正确地做到这一点,我们需要同步另一件事?不好。

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

这样它更简单、更快速、更节省资源。