Monitor.Wait 是否需要同步?

问题描述 投票:0回答:3

我开发了一个通用的生产者-消费者队列,它通过监视器以以下方式发出脉冲:

入队:

    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 版本更好?

c# synchronization monitor producer-consumer
3个回答
7
投票

是的,当前线程需要“拥有”监视器才能调用

Wait
Pulse
,如文档所述。 (所以你还需要锁定
Pulse
。)我不知道为什么需要它的细节,但在Java中是一样的。我通常发现无论如何我都想这样做,以使调用代码干净。

请注意,

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

至于使用

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


3
投票

摘自MSDN对Monitor.Wait()的描述:

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

“释放锁”部分是问题所在,对象没有被锁定。您将 _locker 对象视为 WaitHandle。自己进行可证明正确的锁定设计是一种黑魔法,最好留给我们的药师,例如杰弗里·里希特和乔·达菲。但我会尝试一下:

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 类,它具有更多低开销锁定和自旋等待的黑魔法。


0
投票

查看

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

要使该机制发挥作用,必须避免出现以下顺序的可能性:

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

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

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

  • 等待循环中的代码执行

    Wait
    ,因为其条件不满足。

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

如果双方合作,

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

© www.soinside.com 2019 - 2024. All rights reserved.