不明白是否需要Monitor.Pulse()

时间:2012-02-24 11:23:43

标签: c# multithreading synchronization

根据MSDNMonitor.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);
            }
        }
    }
}

3 个答案:

答案 0 :(得分:35)

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

您使用Wait / Pulse来允许合作通知:我想等待某事发生,所以我进入锁定并致电Wait;通知代码将进入锁定并致电Pulse

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

考虑一下如何实现一个生产者/消费者队列,消费者可以在没有这样的东西的情况下说“当我有一件物品供我消费时叫醒我”。

答案 1 :(得分:9)

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

  

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

     

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

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

答案 2 :(得分:8)

我自己也有同样的怀疑,尽管有一些有趣的答案(其中一些存在于此),我仍然一直在寻找更有说服力的答案。

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

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

也就是说,我认为通过查看 Monitor.Wait 强>方法。

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