C# brought us two interesting tools: the Monitor and the Threadpool. Neither of them was anything new, especially the threadpool.
The monitor is a powerful concept, a generalization of the semaphores, the versatility of which has been demonstrated years ago. The monitor allows to implement conditions that can be signaled, which allows threads to wait for a specific state and then do perform their tasks atomically!
The general approach is to associate a variable, or a consistent set of variables to a monitor instance, and then, use this pattern
... lock(_monitor) { while(!conditionIsMet) { Monitor.Wait(_monitor); } // here we know the conditions are met // and that we have exclusive access // so do some work ... // if we have changed the variables, we can skip it otherwise Monitor.PulseAll(_monitor); }
Its strength lies in the Wait method which releases the lock and then waits for a notification atomically, which is error prone to implement manually. When one is used to work with Mutexes and Events, it takes some time to get accustomed to. But rewards are great because most synchronization patterns can be implemented safely, easily and with maintainable code.
Alas, .Net team missed an opportunity to have a great API which pushed developers in the pit of success.
-
The design should push toward using PulseAll() instead of Pulse()!
They should also probably have secured the use of while(condition) Monitor.Wait();
.
Why all those critics to an otherwise fine piece of API?
Well, using Pulse can lead to deadlocks! My first Can You Spot gave a good example of the risks, but it was obvious. Next one will be devious, but still relying on that weakness.
The problem with Pulse is that you must be sure that only one thread has to be waken up. If this assumption holds true, then you are golden. Otherwise, you are one deadlock away from misery.
The good news is that it is often true, but not always. But this decision shortcut also led to a slightly degenerated monitor pattern:
... lock(_monitor) { if(!conditionIsMet) // notice it was 'while' before. { Monitor.Wait(_monitor); } // here we know the conditions are met // and that we have exclusive access // so do some work ... // if we have changed the variables, we can skip it otherwise Monitor.Pulse(_monitor); }
The difference may look subtle, but suddenly the assumption is now that the condition is true if some other thread signals the monitor, as it is no longer checked after the call to Wait. It may well not be the case, and then you start facing a really nasty bug. The rule is ‘check your condition in a while loop, not a simple if’.
Back to the original topic: PulseAll() vs Pulse(). You may be wondering why .Net provides two variants for that mechanism, especially after my comments regarding the dangers of Pulse(). Well, PulseAll() is probably more expensive than a simple Pulse(), but not by a wide margin either. Looking at Rotor source code, PulseAll() loops through all waiting threads to wake them up all instead of just the first one. As far as I know, there is nothing I consider an issue with PulseAll(), you have one annoyance: PulseAll does not ensure fairness among threads. But that is rarely an issue.
Take away: use the Monitor pattern, it is an excellent synchronization tool, but use it properly, or it will bite you. Well, what doesn’t, when you really think about it 🙂
As a conclusion, for me it is a missed opportunity for a ‘pit of success’ design. I would have proposed to have Pulse() wake all threads and offered a PulseOne method.
2 thoughts on “.Net’s Monitor pattern”