SoFunction
Updated on 2025-03-06

Deeply multi-threading: Detailed explanation of the use of Wait and Pulse

Signaling with Wait and Pulse

I talked about waiting for event handles early on (the thread calling Wait will block until it is not notified by another thread).

Monitor provides a more powerful signal construct with its static methods Wait, Pulse, PulseAll. Using these methods and lock statements, you can implement AutoResetEvent, ManualResetEvent and Semaphore yourself. Even WaitHandle's WaitAll and WaitAny methods are there.

How to use Wait and Pulse?

1: Define a synchronous object, for example:

Readonly object _locker=new object();

2: Define fields in your own blocking condition.

bool _go or int _semaphoreCount;

3: When you want to block, include the following code

lock(_locker)

while(<blocking condition>) //For example while (_go ==false)

(_locker);     //The blocking condition is met and blocking begins.

4: When you want to change the blocking condition, include the following code:

     lock(_locker)

{

//<Change fields in blocking conditions>, such as _go=true;

(_locker); //Or: (_locker); //Notify the change in the state of the thread locked object in the waiting queue.

}

This mode allows you to wait for threads anytime, anywhere. Here is an example where the worker thread will wait until the _go field becomes true.

Copy the codeThe code is as follows:

static readonly object _locker = new object();
        static bool _go;

        internal static void Main()
        {
new Thread(Work).Start(); //The new thread will be blocked because _go == false
(); //Waiting for user input

            lock (_locker)
            {
_go = true; //Change the blocking condition
(_locker); //Notify waiting queue.
            }
        }

        static void Work()
        {
            lock (_locker)
            {
While (!_go) //As long as the _go field is false, wait.
(_locker); //While waiting, the lock has been released.
            }

("Awakened");
        }


For thread safety, ensure that all shared fields are locked when read.

The Work method will keep blocking, waiting for the _go field to become true, and the method does the following operations in order.

1: Release the lock_locker;

2: Block the lock until _locker is "pulsed".

3: Re-acquire the lock on _locker. If the lock has been obtained by other threads, the thread starts blocking until the lock becomes available.

Copy the codeThe code is as follows:

lock(_locker)
{
    While(!_go)
(_locker); //Release the lock
//The lock has been reacquisitioned.
}


If we abandon the pattern, for example remove the while loop. _go field and ReadLine method, etc.:
Copy the codeThe code is as follows:

static object _locker = new object();

        internal static void Main()
        {
            new Thread(Work).Start();
            lock (_locker) (_locker);
        }

        static void Work()
        {
            lock (_locker) (_locker);
("Awakened");
        }


So what is the result of the program running?

In fact, the output is uncertain, and it is possible that you cannot display "wake up".

The main reason is that there is a competition relationship between the main thread and the Work thread. If the Wait method is executed first, it can be displayed normally. However, if the Pulse method is executed first, the pulse will be lost and the worker thread will wait forever. This behavior is different from AutoResetEvent, the Set method of AutoResetEvent has a memory effect, so it still works even if it is called before the WaitOne method.


But Pulse does not have memory function because you want to implement memory function yourself, just like we used the _go flag before,

This is why Wait and Pulse are common: using a boolean flag, we can implement the function of AutoResetEvent, and using an integer flag, we can implement CountdownEvent, Semaphore. Using more complex structures, we can write some more complex structures.