【问题标题】:Why does Monitor class keep 2 queues: "ready" and "waiting"?为什么 Monitor 类保留 2 个队列:“就绪”和“等待”?
【发布时间】:2017-09-19 08:44:40
【问题描述】:

根据MSDN

Monitor 类由静态(在 C# 中)或共享(在 Visual Basic) 对控制访问的对象进行操作的方法 临界区。为每个维护以下信息 同步对象:

  • 对当前持有锁的线程的引用。

  • 就绪队列的引用,其中包含准备好获取锁的线程。

  • waiting 队列的引用,其中包含等待通知锁定状态更改的线程 对象。

在这个thread 中,两个队列引起了一些微妙的问题。

我认为上述线程中问题的根本原因是有 2 个队列。如果只有 ONE 队列,则每当Monitor.Pulse() 时,只能调度该单个队列中的一个线程运行。 多个线程不可能同时处于就绪状态。所以这个问题永远不会发生。

那么为什么 Monitor 保留 2 个队列?

【问题讨论】:

  • @HenkHolterman 感谢您的评论。但实际上我的问题是受到那个问题的启发。该线程的解决方案是正确的。

标签: c# multithreading queue


【解决方案1】:

我认为您误解了那个 SO 帖子。该问题不是由 Monitor 引起的,而是由 that OP's Queue class 中的真正逻辑错误引起的。

Remove 是一个熟悉的模式,它应该使用while 而不是if

lock (q)
{
    // if (q.Count == 0)
    while (q.Count == 0)
    {
        Monitor.Wait(q);
    }
    ... // use it, we are now sure that q.Count > 0
}

然后您可能需要另一种方式(CancellationToken)来结束整个过程。

一个 Monitor 可以有 2 种等待线程,实现者选择使用 2 个队列。使用 1 个队列似乎是可能的,但这不会改变任何事情。它仍然只允许 1 个线程在任何时候运行,所以你的理解在这一点上出现了某种错误。

正在发生的事情是队列中的线程可以具有以下两种状态之一:

  • 当一个线程调用lock(q)并且锁已经被占用时,它被排队为Ready
  • 当一个线程调用 Wait(q) 时,它会被排成 Waiting 并需要一个 Pulse() 来唤醒
  • 调用 Pulse() 时,1 个等待线程移至就绪状态。但它不会立即运行,它必须等待轮到它。根据定义,Pulse() 只能在获得锁时调用。
  • 当脉冲线程重新激活时,情况可能已经改变(即,另一个线程消耗了数据元素)。

Pulse/Wait 机制有点不可靠,当没有线程在 Waiting Pulse 时会被忽视。通常,您不能依靠 Pulse/Wait 进行准确的簿记。 而Monitor的合约不包括fairness

【讨论】:

  • 感谢您的回答。我的问题是受那个启发的。该线程的解决方案是正确的。我只是好奇是否有任何强有力的理由使用 2 个队列而不是一个。希望使用 1 个队列更容易实现。
  • 不,请阅读 Eric Lippert 的答案。算法不正确,我只是添加了 if/while 变量。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-07-07
  • 2020-06-27
  • 2017-03-14
  • 1970-01-01
  • 2012-03-26
  • 2011-01-12
  • 2016-07-13
相关资源
最近更新 更多