不了解 Monitor.Pulse() 的必要性

Posted

技术标签:

【中文标题】不了解 Monitor.Pulse() 的必要性【英文标题】:Don't understand the need for Monitor.Pulse() 【发布时间】:2012-03-14 20:30:05 【问题描述】:

根据MSDN,Monitor.Wait()

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

然而,我所读到的关于 Wait() 和 Pulse() 的所有内容似乎都表明,仅仅释放另一个线程上的锁是不够的。我需要先调用 Pulse() 来唤醒等待的线程。

我的问题是为什么?等待 Monitor.Enter() 上的锁的线程在它被释放时才得到它。没有必要“唤醒他们”。它似乎打败了 Wait() 的用处。

例如。

static object _lock = new Object();

static void Main()

    new Thread(Count).Start();
    Sleep(10);

    lock (_lock)
    
         Console.WriteLine("Main thread grabbed lock");
         Monitor.Pulse(_lock) //Why is this required when we're about to release the lock anyway?
    


static void Count()

    lock (_lock)
     
        int count = 0;

        while(true)
        
            Writeline("Count: " + count++);

            //give other threads a chance every 10th iteration
            if (count % 10 == 0)
                 Monitor.Wait(_lock);
        
    

如果我使用 Exit() 和 Enter() 而不是 Wait() 我可以这样做:

static object _lock = new Object();

static void Main()

    new Thread(Count).Start();
    Sleep(10);

    lock (_lock) Console.WriteLine("Main thread grabbed lock");


static void Count()

    lock (_lock)
     
        int count = 0;

        while(true)
        
            Writeline("Count: " + count++);

            //give other threads a chance every 10th iteration
            if (count % 10 == 0)
            
                 Monitor.Exit(_lock);
                 Monitor.Enter(_lock);
            
        
    

【问题讨论】:

【参考方案1】:

您使用Enter / Exit 获取对锁的独占访问权。

你使用Wait/Pulse允许合作通知:我想等待某事发生,所以我进入锁并调用Wait;通知码将进入锁并调用Pulse

这两个方案是相关的,但它们并没有试图完成同样的事情。

考虑如何实现一个生产者/消费者队列,消费者可以说“当你有一个项目让我消费时叫醒我”,而不需要这样的事情。

【讨论】:

我怀疑我会使用 AutoResetEvent。 Wait/Pulse 是否给了我额外的东西? @GazTheDestroyer:IMO,AutoResetEvent 使用起来更安全。如果未仔细实现 Wait/Pulse 同步,您的等待线程很容易错过脉冲并永远等待。 @GazTheDestroyer:我个人更喜欢Wait/Pulse,我相信它们在某些情况下更有效。与 Groo 不同,我发现编写没有 Wait/Pulse 竞争条件的代码更容易,正是因为 Wait 和 Pulse 都必须在已经拥有监视器。 @GazTheDestroyer:你问 Wait/Pulse 给你什么 AutoResetEvent 没有。 它使您能够编写 AutoResetEvent 就是它的作用。像自动重置事件这样的复杂门必须由 something 构建,如果框架碰巧不能提供您个人需要的复杂门的风格,您将构建它的哪些部分 如果没有等待和脉冲? @EricLippert AutoResetEvent 除外 not 调用“Monitor”或“Pulse”方法。因此,虽然它们可能在某个地方使用相同的内核内部,但 Monitor/Pulse对于这样的 AutoResetEvent 实现来说,并不是严格的“必需方法”。【参考方案2】:

我自己也有同样的疑问,尽管有一些有趣的答案(其中一些出现在这里),但我仍然一直在寻找更有说服力的答案。

我认为关于这个问题的一个有趣而简单的想法是:我可以在没有其他线程等待获取 Monitor.Wait(lockObj) 的特定时刻调用 Monitor.Wait(lockObj) strong>lockObj 对象。我只想等待某些事情发生(例如,某个对象的状态发生变化),这是我知道最终会在其他线程上发生的事情。一旦达到这个条件,我希望能够在其他线程释放它的锁时立即重新获得锁。

根据 Monitor.Wait 方法的定义,它释放锁并尝试再次获取它。如果它在尝试再次获取锁之前没有等待调用 Monitor.Pulse 方法,它会简单地释放锁并立即再次获取它(取决于您的代码,可能在循环中)。

也就是说,我认为通过查看 Monitor.Wait 方法的功能来了解 Monitor.Pulse 方法的需求是很有趣的。

这样想:“我不想释放这个锁并立即尝试再次获取它,因为我不想成为下一个获取这个锁的线程。我也不想停留在一个包含对 Thread.Sleep 的调用的循环中,检查一些标志或其他东西,以便知道我等待的条件何时已达到,以便我可以尝试重新获取锁。我只是想要‘休眠’并自动唤醒,只要有人告诉我我等待的条件已经实现。”。

【讨论】:

谢谢,重要的是要知道“Monitor.Wait”释放了锁,这让我很困惑【参考方案3】:

阅读链接的 MSDN 页面的备注部分:

当一个线程调用Wait时,它会释放对象上的锁并进入对象的等待队列。对象的就绪队列(如果有的话)中的下一个线程获取锁并独占使用该对象。 所有调用 Wait 的线程都保留在等待队列中,直到它们收到锁的所有者发送的来自 Pulse 或 PulseAll 的信号。如果发送 Pulse,则只有等待队列头部的线程受到影响。如果发送 PulseAll,所有等待对象的线程都会受到影响。当收到信号时,一个或多个线程离开等待队列,进入就绪队列。允许就绪队列中的线程重新获取锁。

当调用线程重新获得对象上的锁时,此方法返回。 请注意,如果锁的持有者不调用 Pulse 或 PulseAll,则此方法会无限期阻塞

所以,基本上,当您调用Monitor.Wait 时,您的线程处于等待队列中。要让它重新获得锁,它需要在就绪队列中。 Monitor.Pulse 将等待队列中的第一个线程移动到就绪队列,从而允许它重新获取锁。

【讨论】:

是的,但是为什么要分开“等待”和“准备”队列呢?它有什么优势可以抵消我必须打的额外电话的劣势? +1 表示等待队列:另请参阅 here 以获得简单的图形说明。 @GazTheDestroyer 如果您不使用等待队列,则必须进行主动轮询。在许多情况下,这是不可取的和低效的。请参阅Producer-Consumer-Problem,生产者和消费者以不同的速度工作。

以上是关于不了解 Monitor.Pulse() 的必要性的主要内容,如果未能解决你的问题,请参考以下文章

C#简单理解 Monitor.Wait 与 Monitor.Pulse

使用 lock、Monitor Pulse 和 Wait 同步线程

c# Monitor.wait() 经典实例

将多个 HTTP 请求等待同一个 I/O 完成端口

真的了解js生成随机数吗

谈谈数据决策平台搭建的必要性