Waiting for each other
Waiting for a condition
Sometimes a thread must wait until something becomes true. A thread that takes jobs from a queue has nothing to do while the queue is empty. It could check the queue again and again in a loop, but that burns the CPU and does no useful work. It is better to sleep and be woken when there is a job.
Condition variables
A condition variable is a place where threads sleep until something changes. A thread calls wait to sleep on it. Another thread changes the shared data, then calls signal to wake one sleeper (or broadcast to wake them all). A condition variable is always used with a lock. Wait releases the lock while the thread sleeps, and takes it back before the thread carries on.
Check again after waking
Being woken does not mean the condition is still true. Between the signal and the moment the woken thread runs, another thread may get there first and take the job. So a thread always checks its condition in a while loop: wake up, check again, and go back to sleep if it is still not true. An "if" checks only once, and that is a bug.
Producers and consumers
Producers put items into a shared buffer with a fixed number of slots. Consumers take items out. A producer waits while the buffer is full. A consumer waits while the buffer is empty. Each side signals the other after it changes the buffer: a producer wakes a consumer, and a consumer wakes a producer. Using two condition variables, one for each side, makes sure the right kind of thread is woken.
Semaphores
A semaphore is a counter that threads wait on and post to. Wait takes one from the counter, and sleeps if the counter is 0. Post adds one back and wakes a sleeper. A semaphore that starts at 1 works as a lock. One that starts at 10 can count 10 free slots in a buffer.
Remember
- Don't spin while you wait for a condition; sleep.
- Wait and signal on a condition variable, always with a lock held.
- Check the condition again in a while loop after waking.
- Producers wait when full, consumers wait when empty.
- A semaphore is a counter: start at 1 for a lock, at N to count.