A monitor is a high-level programming language synchronization construct (implemented at the compiler level) designed to make concurrent programming easier and less error-prone than using low-level tools like semaphores.
The defining characteristic of a monitor is automatic mutual exclusion. The compiler guarantees that only one process/thread can be active inside the monitor at any single instant. If a process attempts to call a monitor procedure while another process is already executing inside it, the new process is automatically blocked and placed in the entry queue to wait its turn.
1. Why do we need Condition Variables?
While automatic mutual exclusion is great, it is not enough on its own. Sometimes a process enters the monitor but finds that a required condition is not met (for example, a producer process enters the monitor but finds the shared buffer is full).
If the process just sits there and spins waiting for the buffer to empty, it will block the entire monitor. Because no other process can enter the monitor, a consumer process could never enter to empty the buffer, resulting in a permanent deadlock.
To solve this, monitors introduce condition variables (like x and y). They allow a process to safely suspend itself and release the monitor lock so that another process can enter.
2. How x.wait() and x.signal() Work
Unlike semaphores, condition variables do not have an integer value. They do not count or save signals. There are only two operations you can perform on a condition variable:
- x.wait():
- Action: The executing process is immediately and unconditionally suspended (blocked).
- Queueing: The process is placed into a waiting queue specifically associated with the condition variable
x. - Releasing Lock: Crucially, this operation atomically releases the monitor’s mutual-exclusion lock. This allows other waiting processes (like a consumer) to enter the monitor and change the state.
- x.signal():
- Action: This resumes exactly one process that was blocked in the queue for condition variable
x. - No Memory: If no processes are currently waiting in the queue for
x, calling x.signal() does absolutely nothing (the signal is lost forever). (This is a major difference from semaphores, where signaling always increments a counter, keeping a memory of the signal for future waits).