2

剧透注:问题是最后一句话。

在 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 放在锁定范围之外,它不会编译。但是,这是正确的方法:cf:

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 调用实际上只是标记锁定对象,以便当它超出范围时,此时它实际上“脉冲”条件变量。因为否则,它将有一个最优性问题。

那么,Monitor 对象的设计是如何选择的呢?

编辑:

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

4

2 回答 2

1

您不了解同步的基本问题。什么是“监视器”,线程休眠是什么意思,即将被唤醒是什么意思?

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

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

再加上操作是 PULSE。脉搏是瞬时的。它不会“粘”。脉冲会唤醒那些正在睡觉的人。如果在脉搏之后另一个人检查监视器,它将进入睡眠状态。

现在想象一下:你有一个包含 5 个休眠线程的队列。一个线程(第 6 个)现在想要给它们发送脉冲,而另一个线程(第 7 个)想要检查监视器。

6th 和 7th 并行运行,真正同时运行,因为你有四核 CPU。

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

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

嗯等等。我们只是偶然发现了一个案例,我们想要同步某些东西,并且要正确地做到这一点,我们需要在另一件事上同步?不好。

因此,在您与监视器本身交谈之前,实际的 LOCK 是在外部完成的。这是为了实现SINGLE LOCKING,而不是引入几层分层锁。

这样一来,它更简单、更快、资源更友好。

于 2013-02-01T10:08:44.997 回答
1

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

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

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

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

于 2013-02-01T10:07:20.403 回答