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

Gaz*_*yer 23 c# multithreading synchronization

根据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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我使用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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 35

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

你使用Wait/ Pulse来允许合作通知:我想等待一些事情发生,所以我进入锁定并打电话Wait; 通知代码将进入锁定和通话Pulse.

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

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

  • @GazTheDestroyer:你问Wait/Pulse给你的AutoResetEvent没有.**它使您能够编写AutoResetEvent**它的功能.像自动重置事件这样的复杂门必须由*某些东西*构建而且如果框架没有提供你个人需要的复杂门的味道,*如果没有等待,你要用什么部分构建它?和脉搏? (12认同)
  • @GazTheDestroyer:IMO,`AutoResetEvent`使用起来更安全.如果没有仔细执行等待/脉冲同步,您的等待线程很容易错过脉冲并继续等待. (2认同)

Dan*_*rth 9

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

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

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

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

  • 是的,但是为什么要分开“等待”和“准备”队列呢?它有什么优点可以抵消我必须拨打额外电话的缺点? (2认同)

Mar*_*uda 9

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

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

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

也就是说,我认为这是有趣试图了解的需要Monitor.Pulse通过查看其在的运行有效性的方法Monitor.Wait方法.

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