SoFunction
Updated on 2025-03-07

In-depth multithreading: Analysis of the usage of two-way signals and competitions

Two-Way Signaling and Races
 
An important feature of a method is that it is executed asynchronously, which means calling the pulse method does not block itself waiting for return. If any thread is waiting on the pulsed object, it will not block. In other words, the call will not have any effect on the program, and you can think that the method is ignored.
In this way, Pulse provides a one-way communication: a pulsing thread quietly sends a signal to a waiting thread.
Pulse does not return a value to tell you whether the waiting thread has received a signal.

But sometimes we need to know whether the waiting thread is signaled.For example, the following example:

Copy the codeThe code is as follows:

class Race
    {
        static readonly object _locker = new object();
        static bool  _go;
        public static void MainThread()
        {
            new Thread(SaySomething).Start();
            for (int i = 0; i < 5; i++)
            {
                lock (_locker)
                {
                    _go = true;
(_locker); //Notification waiting queue
                }
            }
        }
        static void SaySomething()
        {
            for (int i = 0; i < 5; i++)
            {
                lock (_locker)
                {
while (!_go) (_locker); //If _go is false, then blocking begins.
                    _go = false;
                    ("Wassup?");
                }
            }
        }
    }

Expected output:
Wassup?
Wassup?
Wassup?
Wassup?
Wassup?

Actual output:

Wassup? (Thread waiting)
 
In the SaySomething method, the for loop is executed to while, at this time _go is false, so you start waiting. In MainThread, the for loop sets _go to true. Then PulseAll. But the PulseAll method is asynchronous.
So before the SaySomething thread is awakened, the for loop in mainThread may have been executed. So the first Wait thread in the SaySomething method receives the message word _go is true, so execute it down and set the _go field to false again. The output is "Wassup?", but the next time the loop has to wait again since _go is false. So the actual output prints a Wassup and starts waiting.
We need the main thread to block if the worker is still executing the previous task in each iteration. When the worker is executed, the main thread resumes execution and then executes iteration.

We can add a _ready flag to control the main thread to have ready before setting the _go flag. That is to say, before setting _go, the main thread will wait for the worker to complete the task, and then wait for the worker to set ready to true. When the worker sets ready to true, the main thread will be notified by pulse.
Copy the codeThe code is as follows:

class Race
    {
        static readonly object _locker = new object();
        static bool _ready, _go;
        public static void MainThread()
        {
            new Thread(SaySomething).Start();
            for (int i = 0; i < 5; i++)
            {
                lock (_locker)
                {
while (!_ready) (_locker); //If the worker's ready is false, wait for the worker.
_ready = false; //Reset flag
                    _go = true;
                    (_locker);
                }
            }
        }
        static void SaySomething()
        {
            for (int i = 0; i < 5; i++)
            {
                lock (_locker)
                {
_ready = true; //Set ready to true
(_locker); //Notify the main thread that the worker has ready and can execute tasks.
                    while (!_go) (_locker);
                    _go = false;
                    ("Wassup?");
                }
            }
        }
    }