# Monitor Pattern

## Problem

Multiple threads must coordinate access to shared state with complex conditions (e.g., "consume only when buffer is non-empty", "produce only when buffer is not full"). Simple mutexes cannot express these conditions without busy-waiting.

## Solution

A **monitor** combines a mutex with one or more **condition variables**. The mutex guards access to the shared state. Condition variables allow threads to efficiently wait for specific predicates to become true and be notified when state changes.

## When to Use

- Bounded buffer / producer-consumer with capacity constraints
- Resource pools where threads wait for available resources
- State machines where transitions depend on complex conditions
- Any scenario requiring "wait until condition X is true"

## When to Avoid

- Lock-free algorithms are available and performance is critical
- Single-threaded or single-producer/single-consumer scenarios
- When message passing (actor model) provides a cleaner abstraction

## Pseudocode

```
monitor BoundedBuffer:
    mutex lock
    condition not_full
    condition not_empty
    buffer = []
    capacity = N

    function put(item):
        lock.acquire()
        while buffer.size == capacity:
            not_full.wait(lock)
        buffer.append(item)
        not_empty.notify()
        lock.release()

    function get():
        lock.acquire()
        while buffer.size == 0:
            not_empty.wait(lock)
        item = buffer.remove_first()
        not_full.notify()
        lock.release()
        return item
```

## Python

```python
import threading
import time
import random

class BoundedBuffer:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.buffer: list = []
        self.condition = threading.Condition()

    def put(self, item: str) -> None:
        with self.condition:
            while len(self.buffer) >= self.capacity:
                print(f"  Buffer full, producer waiting...")
                self.condition.wait()
            self.buffer.append(item)
            print(f"  Buffer: {self.buffer}")
            self.condition.notify_all()

    def get(self) -> str:
        with self.condition:
            while len(self.buffer) == 0:
                print(f"  Buffer empty, consumer waiting...")
                self.condition.wait()
            item = self.buffer.pop(0)
            print(f"  Buffer: {self.buffer}")
            self.condition.notify_all()
            return item

def producer(buf: BoundedBuffer, pid: int) -> None:
    for i in range(4):
        item = f"P{pid}-{i}"
        print(f"[Producer {pid}] Putting {item}")
        buf.put(item)
        time.sleep(random.uniform(0.05, 0.15))

def consumer(buf: BoundedBuffer, cid: int) -> None:
    for _ in range(4):
        item = buf.get()
        print(f"[Consumer {cid}] Got {item}")
        time.sleep(random.uniform(0.1, 0.2))

def main() -> None:
    buf = BoundedBuffer(capacity=3)

    threads = []
    for i in range(2):
        threads.append(threading.Thread(target=producer, args=(buf, i)))
    for i in range(2):
        threads.append(threading.Thread(target=consumer, args=(buf, i)))

    for t in threads:
        t.start()
    for t in threads:
        t.join()

    print("Done.")

if __name__ == "__main__":
    main()
```

## Go

```go
package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

type BoundedBuffer struct {
	buffer   []string
	capacity int
	mu       sync.Mutex
	notFull  *sync.Cond
	notEmpty *sync.Cond
}

func NewBoundedBuffer(capacity int) *BoundedBuffer {
	b := &BoundedBuffer{
		buffer:   make([]string, 0, capacity),
		capacity: capacity,
	}
	b.notFull = sync.NewCond(&b.mu)
	b.notEmpty = sync.NewCond(&b.mu)
	return b
}

func (b *BoundedBuffer) Put(item string) {
	b.mu.Lock()
	defer b.mu.Unlock()

	for len(b.buffer) >= b.capacity {
		fmt.Println("  Buffer full, producer waiting...")
		b.notFull.Wait()
	}
	b.buffer = append(b.buffer, item)
	fmt.Printf("  Buffer: %v\n", b.buffer)
	b.notEmpty.Signal()
}

func (b *BoundedBuffer) Get() string {
	b.mu.Lock()
	defer b.mu.Unlock()

	for len(b.buffer) == 0 {
		fmt.Println("  Buffer empty, consumer waiting...")
		b.notEmpty.Wait()
	}
	item := b.buffer[0]
	b.buffer = b.buffer[1:]
	fmt.Printf("  Buffer: %v\n", b.buffer)
	b.notFull.Signal()
	return item
}

func main() {
	buf := NewBoundedBuffer(3)
	var wg sync.WaitGroup

	// Producers
	for pid := 0; pid < 2; pid++ {
		wg.Add(1)
		go func(pid int) {
			defer wg.Done()
			for i := 0; i < 4; i++ {
				item := fmt.Sprintf("P%d-%d", pid, i)
				fmt.Printf("[Producer %d] Putting %s\n", pid, item)
				buf.Put(item)
				time.Sleep(time.Duration(50+rand.Intn(100)) * time.Millisecond)
			}
		}(pid)
	}

	// Consumers
	for cid := 0; cid < 2; cid++ {
		wg.Add(1)
		go func(cid int) {
			defer wg.Done()
			for i := 0; i < 4; i++ {
				item := buf.Get()
				fmt.Printf("[Consumer %d] Got %s\n", cid, item)
				time.Sleep(time.Duration(100+rand.Intn(100)) * time.Millisecond)
			}
		}(cid)
	}

	wg.Wait()
	fmt.Println("Done.")
}
```

## JavaScript

```javascript
// Conceptual monitor using async/await coordination
class BoundedBuffer {
  constructor(capacity) {
    this.capacity = capacity;
    this.buffer = [];
    this.waitingPuts = [];
    this.waitingGets = [];
  }

  async put(item) {
    while (this.buffer.length >= this.capacity) {
      console.log("  Buffer full, producer waiting...");
      await new Promise((r) => this.waitingPuts.push(r));
    }
    this.buffer.push(item);
    console.log(`  Buffer: [${this.buffer.join(", ")}]`);
    if (this.waitingGets.length > 0) {
      this.waitingGets.shift()();
    }
  }

  async get() {
    while (this.buffer.length === 0) {
      console.log("  Buffer empty, consumer waiting...");
      await new Promise((r) => this.waitingGets.push(r));
    }
    const item = this.buffer.shift();
    console.log(`  Buffer: [${this.buffer.join(", ")}]`);
    if (this.waitingPuts.length > 0) {
      this.waitingPuts.shift()();
    }
    return item;
  }
}

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function producer(buf, pid) {
  for (let i = 0; i < 4; i++) {
    const item = `P${pid}-${i}`;
    console.log(`[Producer ${pid}] Putting ${item}`);
    await buf.put(item);
    await sleep(50 + Math.random() * 100);
  }
}

async function consumer(buf, cid) {
  for (let i = 0; i < 4; i++) {
    const item = await buf.get();
    console.log(`[Consumer ${cid}] Got ${item}`);
    await sleep(100 + Math.random() * 100);
  }
}

async function main() {
  const buf = new BoundedBuffer(3);

  await Promise.all([
    producer(buf, 0),
    producer(buf, 1),
    consumer(buf, 0),
    consumer(buf, 1),
  ]);

  console.log("Done.");
}

main();
```

## Related Patterns

- **Read-Write Lock** — a read-write lock is a more fine-grained monitor that allows concurrent readers.
- **Immutability** — immutable objects need no monitor protection; favour immutability to eliminate synchronisation.
- **Actor Model** — actors avoid monitors entirely by eliminating shared mutable state through message passing.
