7

我开发了一个通用的生产者-消费者队列,它由 Monitor 以下列方式脉冲:

入队:

    public void EnqueueTask(T task)
    {
        _workerQueue.Enqueue(task);
        Monitor.Pulse(_locker);
    }

出队:

private T Dequeue()
    {
        T dequeueItem;
        if (_workerQueue.Count > 0)
        {
               _workerQueue.TryDequeue(out dequeueItem);
            if(dequeueItem!=null)
                return dequeueItem;
        }
        while (_workerQueue.Count == 0)
            {
                Monitor.Wait(_locker);
        }
         _workerQueue.TryDequeue(out dequeueItem);
        return dequeueItem;
    }

等待部分产生以下 SynchronizationLockException :“从未同步的代码块调用对象同步方法”我需要同步它吗?为什么 ?使用 ManualResetEvents 还是 .NET 4.0 的 Slim 版本更好?

4

3 回答 3

6

是的,当前线程需要“拥有”监视器才能调用Waitor Pulse,如文档所述。(所以你也需要锁定Pulse。)我不知道为什么需要它的细节,但它在 Java 中是一样的。我通常发现无论如何我都想这样做,以使调用代码干净。

请注意,Wait释放监视器本身,然后等待Pulse,然后在返回之前重新获取监视器。

至于使用ManualResetEventorAutoResetEvent代替 - 你可以,但我个人更喜欢使用这些Monitor方法,除非我需要等待句柄的一些其他功能(例如原子地等待任何/所有多个句柄)。

于 2010-09-26T13:04:35.000 回答
2

从 Monitor.Wait() 的 MSDN 描述中:

释放对象上的锁并阻塞当前线程,直到它重新获得锁。

“释放锁”部分是问题所在,对象未锁定。您将 _locker 对象视为 WaitHandle。自己设计可证明是正确的锁定设计是一种黑魔法,最好留给我们的药师 Jeffrey Richter 和 Joe Duffy。但我会试一试:

public class BlockingQueue<T> {
    private Queue<T> queue = new Queue<T>();

    public void Enqueue(T obj) {
        lock (queue) {
            queue.Enqueue(obj);
            Monitor.Pulse(queue);
        }
    }

    public T Dequeue() {
        T obj;
        lock (queue) {
            while (queue.Count == 0) {
                Monitor.Wait(queue);
            }
            obj = queue.Dequeue();
        }
        return obj;
    }
}

在大多数实际的生产者/消费者场景中,您都希望限制生产者,使其无法无限制地填充队列。以Duffy 的BoundedBuffer 设计为例。如果您负担得起迁移到 .NET 4.0 的费用,那么您肯定想利用它的 ConcurrentQueue 类,它具有更多具有低开销锁定和自旋等待的黑魔法。

于 2010-09-26T13:41:59.260 回答
0

Monitor.Wait查看and Monitor.Pulse/的正确方法PulseAll不是提供一种等待方式,而是 (for Wait) 作为一种让系统知道代码处于等待循环中的方式,该循环在感兴趣的事物发生变化之前无法退出,并且 ( for Pulse/ PulseAll) 作为让系统知道代码刚刚更改了可能导致满足退出条件的某些其他线程的等待循环的一种手段。一个人应该能够替换所有出现的WaitwithSleep(0)并且仍然让代码正常工作(即使效率低得多,因为花费 CPU 时间反复测试没有改变的条件)。

要使这种机制起作用,必须避免出现以下顺序的可能性:

  • 等待循环中的代码在条件不满足时对其进行测试。

  • 另一个线程中的代码更改条件以使其得到满足。

  • 其他线程中的代码会触发锁(尚未有人等待)。

  • 等待循环中的代码执行 a Wait,因为它的条件不满足。

Wait方法要求等待线程有一个锁,因为这是唯一可以确保它等待的条件在它被测试的时间和代码执行的时间之间不会改变的唯一方法Wait。该Pulse方法需要一个锁,因为这是唯一可以确定如果另一个线程已经“承诺”自己执行 a 的方法WaitPulse直到另一个线程实际这样做之后才会发生。请注意,Wait在锁内使用并不能保证它被正确使用,但在Wait锁外使用不可能是正确的。

如果双方合作, / 设计实际上工作得相当好WaitPulse恕我直言,该设计的最大弱点是(1)没有机制让线程等待多个对象中的任何一个被脉冲;(2) 即使一个对象正在“关闭”一个对象,这样所有未来的等待循环都应该立即退出(可能通过检查退出标志),确保Wait线程已提交给自己的任何对象都将获得 a的唯一方法Pulse是获取锁,可能无限期地等待它变得可用。

于 2013-05-08T16:42:04.183 回答