Click here to Skip to main content
Click here to Skip to main content

Thread synchronization: Wait and Pulse demystified

, 23 Aug 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
An article on the Wait and Pulse methods of the Monitor class.

Table of Contents

Introduction

This short article is about the three less well understood methods of the Monitor class: Wait, Pulse, and PulseAll. The MSDN documentation for these methods explains what they do, but not why or how to use them. I will try to fill this gap and demystify them.

These methods provide low-level synchronization between threads. They are the most versatile constructs, but they are harder to use correctly than other synchronization primitives like AutoResetEvent. However, if you follow the recommended pattern I will describe, they can be used with confidence.

Basics

public partial static class Monitor
{
  public static bool Wait( object o ) { ... } // and overloads
  public static void Pulse( object o ) { ... }
  public static void PulseAll( object o ) { ... }
}

At the most basic level, a thread calls Wait when it wants to enter a wait state, and another thread calls Pulse when it wants to wake up the waiting thread. Note that if a thread calls Pulse when no other threads are waiting, the Pulse is lost. This is in contrast to primitives like AutoResetEvent, where a call to Set is remembered until a subsequent call to WaitOne occurs.

There is a requirement that all these methods must be called from within a lock statement. If they are called while not holding the lock, then they will throw a SynchronizationLockException. We will see why this is actually useful, later on.

For example:

readonly object key = new object();

// thread A
lock ( key ) Monitor.Wait( key );

// thread B
lock ( key ) Monitor.Pulse( key );

If thread A runs first, it acquires the lock and executes the Wait method. Then, thread B runs, releasing thread A to allow it to continue.

Locks

You may have noticed a little problem with the code above. If thread A holds the lock on the key object, why does thread B not block when it tries to acquire the lock? This is, of course, handled properly. The call to Wait in thread A releases the lock before it waits. This allows thread B to acquire the lock and call Pulse. Thread A then resumes, but it has to wait until thread B releases the lock, so it can reacquire it and complete the Wait call. Note that Pulse never blocks.

simple

Queues

The ready queue is the collection of threads that are waiting for a particular lock. The Monitor.Wait methods introduce another queue: the waiting queue. This is required as waiting for a Pulse is distinct from waiting to acquire a lock. Like the ready queue, the waiting queue is FIFO.

simple

Recommended pattern

These queues can lead to unexpected behaviour. When a Pulse occurs, the head of the waiting queue is released and is added to the ready queue. However, if there are other threads in the ready queue, they will acquire the lock before the thread that was released. This is a problem, because the thread that acquires the lock can alter the state that the pulsed thread relies on.

The solution is to use a while condition inside the lock statement:

readonly object key = new object();
bool block = true;

// thread A
lock ( key )
{
  while ( block )
    Monitor.Wait( key );
  block = true;
}

// thread B
lock ( key )
{
  block = false;
  Monitor.Pulse( key );
}

This pattern shows the reason for the rule that locks must be used: they protect the condition variable from concurrent access. Locks are also memory barriers, so you do not have to declare the condition variables as volatile.

The while loop also solves a couple of other problems. Firstly, if thread B executes before thread A calls Wait, the Pulse is lost. However, when thread A eventually runs and acquires the lock, the first thing it does is check the condition. Thread B has already set block to false, so Thread A continues without ever calling Wait.

Secondly, it solves the problem of the queues. If thread A is pulsed, it leaves the waiting queue and is added to the ready queue. If, however, a different thread acquires the lock and this thread sets block back to true, it now doesn't matter. This is because thread A will go round the while loop, find the condition to block is true, and execute Wait again.

Uses

The code above is actually an implementation of an AutoResetEvent. If you omitted the block = true statement in thread A, it would be a ManualResetEvent. If you use an int instead of bool for the condition, it would be a Semaphore. This shows how versatile this pattern is. You have complete control over what condition you use in the while loop.

So, the rule of thumb is: use higher level primitives if they fit. If you need finer control, use the Monitor methods with this pattern to ensure correctness.

Tips

It is usually okay to use PulseAll instead of Pulse. This causes all the waiting threads to re-evaluate their conditions and either continue or go back to waiting. As long as the condition evaluation is not too expensive, this is normally acceptable. PulseAll is also useful where you have multiple threads waiting on the same synchronization object, but with different conditions.

There are overloads of Wait that take a timeout parameter. Similar to above, this usually doesn't hurt. It can help prevent stuck Waits or missed Pulses by re-evaluating the conditions on a regular basis.

Example

Here is an example with full source code that demonstrates the versatility of this pattern. It implements a blocking queue that can be stopped. A blocking queue is a fixed-size queue. If the queue is full, attempts to add an item will block. If the queue is empty, attempts to remove an item will block. When Quit() is called, the queue is stopped. This means that you can't add any more items, but you can remove existing items until the queue is empty. At that point, the queue is finished.

This is a fairly complex set of conditions. You could implement this using a combination of higher-level constructs, but it would be harder. The pattern makes this implementation relatively trivial.

class BlockingQueue<T>
{
  readonly int _Size = 0;
  readonly Queue<T> _Queue = new Queue<T>();
  readonly object _Key = new object();
  bool _Quit = false;

  public BlockingQueue( int size )
  {
    _Size = size;
  }

  public void Quit()
  {
    lock ( _Key )
    {
      _Quit = true;

      Monitor.PulseAll( _Key );
    }
  }

  public bool Enqueue( T t )
  {
    lock ( _Key )
    {
      while ( !_Quit && _Queue.Count >= _Size ) Monitor.Wait( _Key );

      if ( _Quit ) return false;

      _Queue.Enqueue( t );

      Monitor.PulseAll( _Key );
    }

    return true;
  }

  public bool Dequeue( out T t )
  {
    t = default( T );

    lock ( _Key )
    {
      while ( !_Quit && _Queue.Count == 0 ) Monitor.Wait( _Key );

      if ( _Queue.Count == 0 ) return false;

      t = _Queue.Dequeue();

      Monitor.PulseAll( _Key );
    }

    return true;
  }
}

This implementation is safe for concurrent access by an arbitrary number of producers and consumers. Here is an example with one fast producer and two slow consumers:

internal static void Test()
{
  var q = new BlockingQueue<int>( 4 );

  // Producer
  new Thread( () =>
  {
    for ( int x = 0 ; ; x++ )
    {
      if ( !q.Enqueue( x ) ) break;
      Trace.WriteLine( x.ToString( "0000" ) + " >" );
    }
    Trace.WriteLine( "Producer quitting" );
  } ).Start();

  // Consumers
  for ( int i = 0 ; i < 2 ; i++ )
  {
    new Thread( () =>
    {
      for ( ; ; )
      {
        Thread.Sleep( 100 );
        int x = 0;
        if ( !q.Dequeue( out x ) ) break;
        Trace.WriteLine( "     < " + x.ToString( "0000" ) );
      }
      Trace.WriteLine( "Consumer quitting" );
    } ).Start();
  }

  Thread.Sleep( 1000 );

  Trace.WriteLine( "Quitting" );

  q.Quit();
}

And, here is the output of one run:

0.00000000     0000 >     
0.00006564     0001 >     
0.00009096     0002 >     
0.00011540     0003 >     
0.09100076          < 0000     
0.09105981          < 0001     
0.09118936     0004 >     
0.09121715     0005 >     
0.19127709          < 0002     
0.19138214     0006 >     
0.19141905     0007 >     
0.19156006          < 0003     
0.29184034          < 0004     
0.29195839          < 0005     
0.29209006     0008 >     
0.29211268     0009 >     
0.39240077          < 0006     
0.39249521          < 0007     
0.39265713     0010 >     
0.39268187     0011 >     
0.49300483          < 0008     
0.49308145     0012 >     
0.49310759     0013 >     
0.49324051          < 0009     
0.59353358          < 0010     
0.59361452          < 0011     
0.59378797     0014 >     
0.59381104     0015 >     
0.69410956          < 0012     
0.69421405     0016 >     
0.69423932     0017 >     
0.69443953          < 0013     
0.79467082          < 0014     
0.79478532          < 0015     
0.79493624     0018 >     
0.79496473     0019 >     
0.89524573          < 0016     
0.89536309          < 0017     
0.89549100     0020 >     
0.89552164     0021 >     
0.98704302     Quitting     
0.98829663     Producer quitting     
0.99580252          < 0018     
0.99590403          < 0019     
1.09638131          < 0020     
1.09647286          < 0021     
1.19700873     Consumer quitting     
1.19717586     Consumer quitting

Conclusion

Well, that's about it. I hope I have removed some of the mystery that seems to surround Wait and Pulse, and filled in the gaps left by MSDN.

Thanks for reading this article; I hope you liked it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Nicholas Butler

United Kingdom United Kingdom

I built my first computer, a Sinclair ZX80, on my 11th birthday in 1980.
In 1992, I completed my Computer Science degree and built my first PC.
I discovered C# and .NET 1.0 Beta 1 in late 2000 and loved them immediately.
I have been writing concurrent software professionally, using multi-processor machines, since 1995.
 
In real life, I have spent 3 years travelling abroad,
I have held a UK Private Pilots Licence for 20 years,
and I am a PADI Divemaster.
 
I now live near idyllic Bournemouth in England.
 
If you would like help with multithreading, please contact me via my website:
 
 
I can work 'virtually' anywhere!

Comments and Discussions

 
You must Sign In to use this message board.
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberRDS Chauhan1-Dec-11 15:49 
An excellent article.
GeneralMy vote of 4memberSergiy Tkachuk19-Oct-11 14:16 
Very clear article!
Just some comments in the usege of BlockingQueue could be useful to save time to investigate the code.
QuestionWhat about ReaderWriterLockSlimmemberDeusiro17-Sep-10 13:15 
What about the class ReaderWriterLockSlim?
sorry for the newbie question. Im looking for the best solution in c# for the producer-consumer problem. I have only 2 threads.
Pedro

GeneralMy vote of 5membernoshirwan1-Sep-10 10:12 
Simply Excellent!!! The closest I can say is of Albahari of O'Rielly books. That is 90% as good as yours. Keep it up.
Noshirwan
GeneralIt’s really “demystifying” :)memberemre200826-Sep-09 9:05 
As you said in your intro part; “…MSDN documentation for these methods explains what they do, but not why or how to use them… “ you’re definitely right. Also I couldn’t find a neat explanation (like your’s) about this subject when I Googled it (neither in English nor in my native language Turkish). Although there are so many complex examples in many of the coding web sites, it is really hard to understand from those examples why and how to use them, especially for a beginner like me.
Thank you Mr. Nick Butler Thumbs Up | :thumbsup:
GeneralRe: It’s really “demystifying” :)mentorNick Butler27-Sep-09 2:19 
You're welcome. Thanks for the comments Smile | :)
 
Nick
 
----------------------------------
Be excellent to each other Smile | :)

Generalvery usefulmemberDong Xiang28-Jul-09 11:25 
I looked at the MSDN but still don't get the meaning of Wait and Pulse. This article saved a lot of my time.
GeneralRe: very usefulmentorNick Butler28-Jul-09 11:39 
Glad you liked it Smile | :)
 
Nick
 
----------------------------------
Be excellent to each other Smile | :)

GeneralExactly what I was looking for!memberrony96-May-09 3:50 
I've been looking for some ways to replace Suspend() and Resume() in my program and your Recommended pattern was exactly what I was looking for. Very helpful all around article. Many thanks!
GeneralEquivalent of WaitHandle.WaitAllmemberDmitri Nesteruk27-Nov-08 4:30 
Could you please show an example of how to do the equivalent of WaitHandle.WaitAll with Pulse and Wait? Thanks.
AnswerRe: Equivalent of WaitHandle.WaitAllmemberNick Butler27-Nov-08 12:25 
Hi Dmitri.
 
To WaitAll, just use the && operator in your while loop:
 
bool p1 = false; // predicate 1
bool p2 = false; // predicate 2

...
 
lock( key )
  while( !( p1 && p2 && ... ) )
    Monitor.Wait( key );
 
Nick
 
----------------------------------
Be excellent to each other Smile | :)

GeneralRe: Equivalent of WaitHandle.WaitAllmembersupercat97-Jul-09 6:53 
(I know this thread was last posted many months ago, but in case you're still around...)
 
I wish to allow a queue to be read by several threads with separate queue-reader objects (each queue-reader object holds its own pointers, so each thread would effectively see its own copy of the queue even though the data is not duplicated). If a thread is doing nothing but waiting for data from the queue, Monitor.Wait and Monitor.PulseAll work very nicely. What would be the best ways, though, of handling the following scenarios:
 
-1- A thread is waiting for data from a queue, and the object on behalf of which the thread is operating gets disposed.
 
-2- A thread needs to wait until data arrives on either of two queues.
 
My inclination would be to have each object which will have a thread or threads operating on its behalf hold an object reference for each such thread, which would be set to whatever monitor object the thread was waiting upon, doing something like:
' In code that waits
  myWaitObject = thisQueue.Waiter
  Monitor.Wait(thisQueue.Waiter)
  myWaitObject = someHarmlessObject
' In code that wants to force a wake-up for disposal or whatever
  Monitor.PulseAll(myWaitObject)
That would cause everybody who was waiting on the queue to be awakened briefly, but hopefully that wouldn't happen so much as to cause a performance degradation.
 
For the second scenario, I could either allow multiple queues to share a common monitor object, I could allow each queue to hit multiple monitor objects, or I could simply have queues do a periodic wake-up and go back to sleep if nothing happens. Aesthetically, I like the idea of not having to have the queue maintain a list of subscribers, since subscription models can be prone to memory leaks.
GeneralLock free Blocking queuememberSaar Yahalom29-Aug-08 1:21 
Hi Nick,
 
Very well written, got my five. There is an implementation in the new Parallel Extensions to the .NET Framework[^] of a lock free blocking queue.
This could be of an interest to you, as it lets you enqueue and dequeue without using locks using Compare&Swap operations.
 
Saar Yahalom.

GeneralRe: Lock free Blocking queuememberNick Butler29-Aug-08 3:26 
Thanks Saar Big Grin | :-D
 
Yes, I've been playing with the PFX CTPs.
 
Also, Joe Duffy recently posted a work stealing queue with explanation on his blog[^]. Very interesting.
 
Nick
 
----------------------------------
Be excellent to each other Smile | :)

GeneralVery very good sirmvpSacha Barber25-Aug-08 23:15 
Very very good sir, I salute you. 5 all the way
 
Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralRe: Very very good sirmemberNick Butler26-Aug-08 5:56 
Thanks Sacha Big Grin | :-D
 
A Platinum 5 is always welcome Cool | :cool:
 
----------------------------------
Be excellent to each other Smile | :)

GeneralRe: Very very good sirmvpSacha Barber26-Aug-08 6:00 
platinum 5?
 
Anyway this, like all your stuff, is way cool.
 
Thanks
 
Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralRe: Very very good sirmemberNick Butler26-Aug-08 6:05 
Sacha Barber wrote:
platinum 5?

 
A 5 from a platinum member!
 
Thanks very much Big Grin | :-D
 
----------------------------------
Be excellent to each other Smile | :)
modified on Friday, August 29, 2008 5:46 AM

GeneralRe: Very very good sirmvpSacha Barber26-Aug-08 6:52 
Glad to be at your service then.
 
Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralGood stuffmemberMR_SAM_PIPER25-Aug-08 15:01 
Great article. I really like the blocking queue implementation, that's a useful piece of code, and surprisingly straight-forward too.
GeneralRe: Good stuffmemberNick Butler26-Aug-08 5:54 
Thanks Sam Big Grin | :-D
 
----------------------------------
Be excellent to each other Smile | :)

GeneralGreat article!memberrht34124-Aug-08 6:50 
Nick, thank you for this article...well written.
GeneralRe: Great article!memberNick Butler24-Aug-08 8:34 
Thanks Big Grin | :-D
 
----------------------------------
Be excellent to each other Smile | :)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 23 Aug 2008
Article Copyright 2008 by Nicholas Butler
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid