Multithreading: condition variables
There are times when a thread needs to wait for a specific condition to be true. Since this condition tends to involve shared state, then you know that you must use some sort of synchronization to ensure proper update of the data used by the condition. When we looked at kernel objects, we’ve seen that there was a method (SignalAndWait) that you can use to signal a kernel object and wait on another atomically. This is good and helps with those nasty race conditions that might occur if you have to signal one object and wait on another.
Now, when you use the sync primitives (CLR locks and reader/writer locks), we stop having access to the kernel objects used by those high level primitives! This means that we’re back to the race condition when we need to, for instance, exit a critical section and set an event. And this is the problem that condition variables solve.
In .NET, condition variables have first class support, through the Monitor’s Wait and Pulse methods. Calling the static Wait method results in releasing the lock on an object and blocking the current thread until it reacquires that lock (in practice, this means that the thread that called Wait goes into the waiting queue of that object). When the method returns, it will have reacquired the lock (something that is only possible when another thread calls the Pulse or PulseAll method).
It’s important to understand that you can only call Wait over an object for which the calling thread owns the lock (failing to respect this results in a SynchronizationLockException being thrown). The same is true for the Pulse and PulseAll methods. In practice, this means that Wait, Pulse and PulseAll methods must be invoked from within a lock block!
Notice that since the Wait method needs to reacquire the lock, it will block until it achieves that purpose (even if you use one of overloads that expect a timeout). This means that you’ll have to use the return values to check if the wait ended before the specified timeout (in which case, true is returned).
As we’ve said, you need to signal a condition by calling the Pulse/PulseAll methods. This is the only way to unblock a thread that has gone into the waiting queue of an object. On the next post, we’ll see a practical example of how to use this feature. Keep tuned!