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:

  1. Mutual Exclusion: Only one thread can be active inside the monitor at a time.
  2. 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 while loops! 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.

  1. Entry Set: Waiting to acquire the lock.
  2. Owner: Executing inside the monitor.
  3. Wait Set: Waiting for a condition (released lock).
Entry Set (Queue)
Monitor (Owner)
Wait Set (CV)
System Empty.

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)
}