30

According to MSDN, Monitor.Wait():

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

However, everything I have read about Wait() and Pulse() seems to indicate that simply releasing the lock on another thread is not enough. I need to call Pulse() first to wake up the waiting thread.

My question is why? Threads waiting for the lock on a Monitor.Enter() just get it when it's released. There is no need to "wake them up". It seems to defeat the usefulness of Wait().

eg.

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

If I use Exit() and Enter() instead of Wait() I can do:

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);
            }
        }
    }
}
GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103

3 Answers3

48

You use Enter / Exit to acquire exclusive access to a lock.

You use Wait / Pulse to allow co-operative notification: I want to wait for something to occur, so I enter the lock and call Wait; the notifying code will enter the lock and call Pulse.

The two schemes are related, but they're not trying to accomplish the same thing.

Consider how you'd implement a producer/consumer queue where the consumer can say "Wake me up when you've got an item for me to consume" without something like this.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I suspect I'd use an AutoResetEvent. Does Wait/Pulse give me something extra? – GazTheDestroyer Feb 24 '12 at 11:37
  • 3
    @GazTheDestroyer: IMO, `AutoResetEvent` is much safer to use. If Wait/Pulse synchronization is not implemented carefully, your waiting thread can easily miss the pulse and continue waiting forever. – vgru Feb 24 '12 at 11:44
  • 1
    @GazTheDestroyer: Personally I prefer Wait/Pulse, and I believe they're more efficient in some cases. Contrary to Groo, I find it *easier* to write code which doesn't have race conditions for Wait/Pulse, precisely because both Wait and Pulse have to be called within the context of a thread which already owns the monitor. – Jon Skeet Feb 24 '12 at 11:47
  • I guess it's a matter of preference. I've found race conditions in Wait/Pulse code in a couple of examples. Even the [MSDN example for Monitor.Pulse](http://msdn.microsoft.com/en-us/library/system.threading.monitor.pulse(v=vs.90).aspx) has a deadlock, which shows how subtle this construct is. [This post](http://stackoverflow.com/a/1355415/69809) summarizes my opinions nicely. – vgru Feb 24 '12 at 12:03
  • 16
    @GazTheDestroyer: You ask what Wait/Pulse gives you that AutoResetEvent does not. **It gives you the ability to write AutoResetEvent** is what it does. Complex gates like auto reset events have to be built out of *something* and if the framework does not happen to provide the flavour of complex gate that you personally need, *what parts are you going to build it out of* if there's no Wait and Pulse? – Eric Lippert Feb 24 '12 at 16:54
  • @Groo: When used properly, Wait/Pulse don't have race conditions, because the code that uses `Wait` will first check *within the lock* whether there's anything for it to do yet (if so, it won't wait), and code which might give a waiting thread something to do will do so within the lock and then call `Pulse` or `PulseAll`. If someone's waiting, it will get pulsed. If nobody is waiting, the next thread to come along will enter the lock and see that there's work already waiting. There's no way the second thread can do its `Pulse` between the first thread's condition check and its wait. – supercat Jan 25 '13 at 21:54
  • @supercat: "when used properly", nothing has race conditions. But I don't see why you think that no thread can call `Pulse` before a different thread calls `Wait`. For example, check the [MSDN example for `Monitor.Pulse`](http://msdn.microsoft.com/en-us/library/system.threading.monitor.pulse(v=vs.90).aspx), and tell me what happens if `SecondThread` method runs before the `FirstThread` method. – vgru Jan 27 '13 at 20:28
  • @Groo: The proper way to write that code would have been to have `FirstThread` only wait if the queue isn't empty, and to have the second thread only wait if it is; if there would be a possibility that more than two threads might want to use that code, one should probably use `PulseAll` any time the queue status changes. – supercat Jan 27 '13 at 20:43
  • @Groo: That example is pretty horrible, really; a much better example would have had the first thread generate items outside the lock, then enter the lock and queue them (waiting if the queue had more than 12 items already), and had the second thread read items within the lock and process them outside it. BTW, I wish there were a mechanism via which one could pass an Int32 to `Pulse` and a pair of Int32 to `Wait`, such that the `Wait` would only exit if the value passed to `Pulse`, masked with the first `Wait` value, equaled the second. That would facilitate this type of scenario. – supercat Jan 27 '13 at 20:53
  • @supercat: Yeah, it's ironic that the official resource for .NET fails to do it right. That was basically the reason for my first comment, I was just saying that `Wait`/`Pulse` in a `Monitor` are slightly more prone to subtle errors than `Set`/`WaitOne` in an `AutoResetEvent`. – vgru Jan 27 '13 at 21:00
  • @Groo: I think a key to using them correctly is to write code to work even if `Wait` arbitrarily decides to wake up early. Think of `Wait` as saying "I could keep retesting a condition in this loop (releasing and reacquiring the lock each time through) but unless someone else has done something that might change the condition, I may as well not bother. – supercat Jan 27 '13 at 21:11
  • @JonSkeet, thanks, again, for a useful answer. Why should the calls to `Monitor.Wait` and `Monitor.Pulse` be bounded by a `lock(syncLock)`? Why is it required on the `Wait-Pulse` model? – Bliss Jun 19 '16 at 13:20
  • @Bliss: It's been a while since I thought through it, but IIRC it makes it easier to avoid situations where one thread waits "just after" a pulse, but without checking the "please wait" condition which is almost always required. – Jon Skeet Jun 19 '16 at 20:20
  • @JonSkeet, thanks much for your answer. So, the surrounding `lock` statement is required only initially, i.e. only for the 1st acquiring of the lock? { Otherwise, the combination wouldn't make sense (to me): The `Monitor.Wait` call releases the lock, so what would be the meaning of calling it within a `lock` statement on the same lock it releases? } – Bliss Jun 19 '16 at 21:23
  • @Bliss: It reacquires the lock before it returns. At this point, I think if you have any more questions it would be better to ask in a new post. – Jon Skeet Jun 20 '16 at 05:37
  • 1
    @EricLippert Except AutoResetEvent does *not* call the 'Monitor' or 'Pulse' methods.. so while they may use the same kernel internals somewhere down the line, Monitor/Pulse are not strictly 'required methods' for such an AutoResetEvent implementation. – user2864740 Jun 21 '18 at 03:30
14

I myself had this same doubt, and despite some interesting answers (some of them present here), I still kept searching for a more convincing answer.

I think an interesting and simple thought on this matter would be: I can call Monitor.Wait(lockObj) at a particular moment in which no other thread is waiting to acquire a lock on the lockObj object. I just want to wait for something to happen (some object's state to change, for instance), which is something I know that will happen eventually, on some other thread. As soon as this condition is achieved, I want to be able to reacquire the lock as soon as the other thread releases its lock.

By the definition of the Monitor.Wait method, it releases the lock and tries to acquire it again. If it didn't wait for the Monitor.Pulse method to be called before trying to acquire the lock again, it would simply release the lock and immediately acquire it again (depending on your code, possibly in loop).

That is, I think it's interesting trying to understand the need of the Monitor.Pulse method by looking at its usefulness in the functioning of the Monitor.Wait method.

Think like this: "I don't want to release this lock and immediately try to acquire it again, because I DON'T WANT to be ME the next thread to acquire this lock. And I also don't want to stay in a loop containing a call to Thread.Sleep checking some flag or something in order to know when the condition I'm waiting for has been achieved so that I can try to reacquire the lock. I just want to 'hibernate' and be awaken automatically, as soon as someone tells me the condition I'm waiting for has been achieved.".

Marcos Arruda
  • 532
  • 7
  • 15
10

Read the Remarks section of the linked MSDN page:

When a thread calls Wait, it releases the lock on the object and enters the object's waiting queue. The next thread in the object's ready queue (if there is one) acquires the lock and has exclusive use of the object. All threads that call Wait remain in the waiting queue until they receive a signal from Pulse or PulseAll, sent by the owner of the lock. If Pulse is sent, only the thread at the head of the waiting queue is affected. If PulseAll is sent, all threads that are waiting for the object are affected. When the signal is received, one or more threads leave the waiting queue and enter the ready queue. A thread in the ready queue is permitted to reacquire the lock.

This method returns when the calling thread reacquires the lock on the object. Note that this method blocks indefinitely if the holder of the lock does not call Pulse or PulseAll.

So, basically, when you call Monitor.Wait, your thread is in the waiting queue. For it to re-acquire the lock, it needs to be in the ready queue. Monitor.Pulse moves the first thread in the waiting queue to the ready queue and thus allows for it to re-acquire the lock.

Community
  • 1
  • 1
Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • 2
    Yeah, but WHY separate "waiting" and "ready" queues? What advantage does it give to offset the disadvantage of an extra call I have to make? – GazTheDestroyer Feb 24 '12 at 11:30
  • 1
    +1 for waiting queue: See also [here](http://www.codeproject.com/Articles/28785/Thread-synchronization-Wait-and-Pulse-demystified) for a simple graphical explanation. – AlexS Feb 24 '12 at 11:33
  • @GazTheDestroyer If you'd not use the waiting queue you would have to do active polling. For many situations this is not desireable and inefficent. See [Producer-Consumer-Problem](http://en.wikipedia.org/wiki/Producer-consumer_problem) with producer and consumer working with different speeds. – AlexS Feb 24 '12 at 11:42