Monitors and Channels
[!NOTE] This module explores the core principles of Monitors and Channels, deriving solutions from first principles and hardware constraints to build world-class, production-ready expertise.
1. The Evolution of Synchronization
Semaphores are powerful but dangerous. A single missing V() call can freeze the entire system.
Monitors were invented to make synchronization safer and more structured.
What is a Monitor?
A Monitor is a synchronization construct that enforces:
- Mutual Exclusion: Only one thread can be active inside the monitor at a time.
- Encapsulation: Shared data is hidden and only accessible via monitor procedures.
In Java, every Object is a Monitor (via synchronized).
Condition Variables
Monitors use Condition Variables to allow threads to wait for a specific state (e.g., “Buffer Not Empty”).
- Wait(): Release the lock and go to sleep in the Wait Set.
- Signal() / Notify(): Wake up one thread from the Wait Set and move it to the Entry Set.
[!IMPORTANT] Always use
whileloops! When a thread wakes up, the condition might have changed again (MESA semantics).while (buffer.isEmpty()) { wait(); }
2. Interactive: Monitor Lifecycle
Visualize the journey of a thread through a Monitor.
- Entry Set: Waiting to acquire the lock.
- Owner: Executing inside the monitor.
- Wait Set: Waiting for a condition (released lock).
3. Go Channels: CSP
Go takes a different approach based on CSP (Communicating Sequential Processes). Instead of locking shared memory, goroutines share data by sending it through Channels.
“Do not communicate by sharing memory; instead, share memory by communicating.”
Unbuffered vs Buffered
- Unbuffered: Synchronous. Sender blocks until Receiver is ready. Handshake.
- Buffered: Asynchronous (up to capacity). Sender only blocks if buffer is full.
4. Code Example: Monitors vs Channels
class MessageQueue {
private String message;
private boolean empty = true;
public synchronized String take() throws InterruptedException {
while (empty) {
wait(); // Wait for message
}
empty = true;
notifyAll();
return message;
}
public synchronized void put(String message) throws InterruptedException {
while (!empty) {
wait(); // Wait for empty slot
}
this.message = message;
empty = false;
notifyAll();
}
}
package main
import "fmt"
func main() {
messages := make(chan string)
go func() {
messages <- "ping" // Blocks until received
}()
msg := <-messages // Blocks until sent
fmt.Println(msg)
}