semantics: wait(m): place self on a wait queue, and releases lock m. these two steps must be atomic. when this wait is woken up, it re- signal(): if no thread is waiting, this is a no-op. If there are threads waiting, wake up one of them. broadcast(): wake up all waiting threads mesa semantics: signaling thread continues, and when it exits the monitor, the waiting thread may enter monitor. however, there is a potential race that another thread comes in and enters the monitor first before the waiting thread. this is fine. allowed by mesa semantics. to avoid race, the waiting thread must re-check the condition it waits. turns out, implementing CV on top of semaphore is quite tricky because of the semantics of signal() and broadcast(): they wake up exactly the threads that are waiting, and if a new thread comes in and tries to wait after the signal() or broadcast() call, the new thread should wait. this actually looks like the original hoare's semantics that requires a direct transfer from the signaling thread to the waiting thread woken, because we want to wake up exactly the threads that are waiting. Andrew[1] gives two solutions. One uses another semaphore to ensure that the broadcasting thread waits for all waiting threads to wake up, the other explicitly manages a wait queue. Do we need to hold the monitor lock before calling signal() or broadcast()? Although the semantics doesn't require, all existing implementations require holding a lock. This is to avoid losing wake-ups: the signaling thread may signal before a waiting thread puts itself onto a wait queue. We can use a lock internal to a condition variable, as shown in [1], but all existing implementations seem to use an external lock (e.g. C#, Java, pthread condition variable). [1] Implementing Condition Variables with Semaphores. Andrew Birrell.