Monitor.Wait 是否需要同步?

发布于 2024-09-25 03:16:09 字数 788 浏览 5 评论 0原文

我开发了一个通用的生产者-消费者队列,它按以下方式由 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 还是 Slim 版本的 .NET 4.0 更好?

I have developed a generic producer-consumer queue which pulses by Monitor in the following way:

the enqueue :

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

the dequeue:

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;
    }

the wait section produces the following SynchronizationLockException :
"object synchronization method was called from an unsynchronized block of code"
do i need to synch it? why ? Is it better to use ManualResetEvents or the Slim version of .NET 4.0?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

耳根太软 2024-10-02 03:16:09

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

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

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

Yes, the current thread needs to "own" the monitor in order to call either Wait or Pulse, as documented. (So you'll need to lock for Pulse as well.) I don't know the details for why it's required, but it's the same in Java. I've usually found I'd want to do that anyway though, to make the calling code clean.

Note that Wait releases the monitor itself, then waits for the Pulse, then reacquires the monitor before returning.

As for using ManualResetEvent or AutoResetEvent instead - you could, but personally I prefer using the Monitor methods unless I need some of the other features of wait handles (such as atomically waiting for any/all of multiple handles).

终难愈 2024-10-02 03:16:09

来自Monitor.Wait()的MSDN描述:

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

“释放锁”部分是问题所在,对象没有被锁定。您将 _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 类,它具有更多低开销锁定和自旋等待的黑魔法。

From the MSDN description of Monitor.Wait():

Releases the lock on an object and blocks the current thread until it reacquires the lock.

The 'releases the lock' part is the problem, the object isn't locked. You are treating the _locker object as though it is a WaitHandle. Doing your own locking design that's provably correct is a form of black magic that's best left to our medicine men, like Jeffrey Richter and Joe Duffy. But I'll give this one a shot:

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;
    }
}

In most any practical producer/consumer scenario you will want to throttle the producer so it cannot fill the queue unbounded. Check Duffy's BoundedBuffer design for an example. If you can afford to move to .NET 4.0 then you definitely want to take advantage of its ConcurrentQueue class, it has lots more black magic with low-overhead locking and spin-waiting.

隱形的亼 2024-10-02 03:16:09

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

为了使这种机制发挥作用,必须避免出现以下顺序的可能性:

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

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

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

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

Wait 方法要求等待线程拥有锁,因为这是确保其等待的条件在测试时间和代码执行时间之间不会改变的唯一方法。 等等Pulse 方法需要锁,因为这是它可以确定是否另一个线程“承诺”执行 Wait 的唯一方法,Pulse code> 直到其他线程实际执行此操作后才会发生。请注意,在锁内使用 Wait 并不能保证它的使用正确,但在锁外使用 Wait 不可能是正确的。

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

The proper way to view Monitor.Wait and Monitor.Pulse/PulseAll is not as providing a means of waiting, but rather (for Wait) as a means of letting the system know that the code is in a waiting loop which can't exit until something of interest changes, and (for Pulse/PulseAll) as a means of letting the system know that code has just changed something that might cause satisfy the exit condition some other thread's waiting loop. One should be able to replace all occurrences of Wait with Sleep(0) and still have code work correctly (even if much less efficiently, as a result of spending CPU time repeatedly testing conditions that haven't changed).

For this mechanism to work, it is necessary to avoid the possibility of the following sequence:

  • The code in the wait loop tests the condition when it isn't satisfied.

  • The code in another thread changes the condition so that it is satisfied.

  • The code in that other thread pulses the lock (which nobody is yet waiting on).

  • The code in the wait loop performs a Wait since its condition wasn't satisfied.

The Wait method requires that the waiting thread have a lock, since that's the only way it can be sure that the condition it's waiting upon won't change between the time it's tested and the time the code performs the Wait. The Pulse method requires a lock because that's the only way it can be sure that if another thread has "committed" itself to performing a Wait, the Pulse won't occur until after the other thread actually does so. Note that using Wait within a lock doesn't guarantee that it's being used correctly, but there's no way that using Wait outside a lock could possibly be correct.

The Wait/Pulse design actually works reasonably well if both sides cooperate. The biggest weaknesses of the design, IMHO, are (1) there's no mechanism for a thread to wait until any of a number of objects is pulsed; (2) even if one is "shutting down" an object such that all future wait loops should exit immediately (probably by checking an exit flag), the only way to ensure that any Wait to which a thread has committed itself will get a Pulse is to acquire the lock, possibly waiting indefinitely for it to become available.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文