Skip to main content

Overview

A Monitor is a high-level synchronization construct that encapsulates:
  1. Shared data (protected state)
  2. Mutual exclusion (implicit mutex)
  3. Condition variables (for waiting and signaling)
Monitors provide a cleaner, safer abstraction than manually managing mutexes and condition variables.

When to Use Monitors

Readers-Writers

Coordinating multiple readers with exclusive writers

Producer-Consumer

Managing bounded buffers with complex synchronization logic

Resource Allocation

Implementing fair resource allocation policies

Complex Protocols

Any scenario with intricate waiting conditions and state transitions

How It Works

Monitors enforce mutual exclusion automatically:
  • Only one thread can be executing inside the monitor at a time
  • Threads wait on condition queues when conditions aren’t met
  • When a thread exits or waits, it can signal other threads to wake up

Monitor Structure

1

Entry

Thread calls monitor method (implicitly acquires mutex)
2

Condition Check

Thread checks if it can proceed
3

Wait or Proceed

If condition false, wait on condition queue. Otherwise, execute critical section.
4

State Change

Thread modifies shared state
5

Signal

Thread signals other threads that condition may have changed
6

Exit

Thread exits monitor (implicitly releases mutex)

Implementation: Library Monitor

The simulator implements a readers-writers monitor for the library scenario:
source/js/core/monitorLibrary.js
// Monitor para problema lectores-escritor en biblioteca.
export class LibraryMonitor {
  constructor() {
    // Cantidad de lectores activos dentro de la biblioteca.
    this.activeReaders = 0;
    // true cuando el bibliotecario esta escribiendo catalogo.
    this.writerActive = false;
    // Cola FIFO de lectores bloqueados.
    this.readerQueue = [];
    // Cola FIFO de escritores bloqueados.
    this.writerQueue = [];
    // Lectores despertados que ya tienen permiso concedido para entrar.
    this.readerGranted = new Set();
    // Escritor despertado que ya tiene permiso concedido para entrar.
    this.writerGranted = null;
  }

  // Lector pide entrar: solo bloquea si hay escritor activo o esperando con prioridad.
  enterRead(thread) {
    // Si fue despertado por el monitor, entra directo sin volver a bloquearse.
    if (this.readerGranted.has(thread)) {
      this.readerGranted.delete(thread);
      this.activeReaders += 1;
      return true;
    }

    // Doy prioridad al escritor esperando para evitar inanicion de escritura.
    const writerWaiting = this.writerQueue.length > 0;
    if (!this.writerActive && !writerWaiting) {
      this.activeReaders += 1;
      return true;
    }

    thread.state = "blocked";
    thread.blockedBy = this;
    if (!this.readerQueue.includes(thread)) this.readerQueue.push(thread);
    return false;
  }

  // Lector sale y, si era el ultimo, intenta despertar escritor.
  exitRead() {
    if (this.activeReaders > 0) this.activeReaders -= 1;
    return this.wakeUpThreads();
  }

  // Escritor pide entrar: solo entra si no hay lectores ni otro escritor.
  enterWrite(thread) {
    // Si el monitor ya le concedio paso al escritor, entra directo.
    if (this.writerGranted === thread) {
      this.writerGranted = null;
      this.writerActive = true;
      return true;
    }

    if (!this.writerActive && this.activeReaders === 0) {
      this.writerActive = true;
      return true;
    }

    thread.state = "blocked";
    thread.blockedBy = this;
    if (!this.writerQueue.includes(thread)) this.writerQueue.push(thread);
    return false;
  }

  // Escritor sale y despierta siguiente segun politica del monitor.
  exitWrite() {
    this.writerActive = false;
    return this.wakeUpThreads();
  }

  // Politica: primero escritor si hay alguno; si no, despierta todos los lectores.
  wakeUpThreads() {
    const awakened = [];

    if (!this.writerActive && this.activeReaders === 0 && this.writerQueue.length > 0) {
      const nextWriter = this.writerQueue.shift();
      nextWriter.state = "ready";
      nextWriter.blockedBy = null;
      // Nota: aqui solo doy permiso; ENTER_WRITE consumira este permiso.
      this.writerGranted = nextWriter;
      awakened.push(nextWriter);
      return awakened;
    }

    if (!this.writerActive && this.writerQueue.length === 0 && this.readerQueue.length > 0) {
      while (this.readerQueue.length > 0) {
        const reader = this.readerQueue.shift();
        reader.state = "ready";
        reader.blockedBy = null;
        // Igual que escritor: concedo permiso y lo consumira ENTER_READ.
        this.readerGranted.add(reader);
        awakened.push(reader);
      }
    }

    return awakened;
  }
}

Readers-Writers Problem

The monitor solves the classic readers-writers problem:
  • Readers: Can access resource simultaneously (multiple students reading)
  • Writers: Need exclusive access (librarian updating catalog)

Synchronization Rules

1

Multiple Readers

Multiple readers can enter simultaneously if no writer is active or waiting
2

Exclusive Writer

Only one writer, and no readers, can be active at once
3

Writer Priority

If a writer is waiting, new readers must wait to prevent writer starvation
4

Fair Queuing

Separate FIFO queues for readers and writers

Enter Read Operation

The enterRead() method implements reader entry logic:
If the monitor previously granted permission (via wake-up), enter immediately:
if (this.readerGranted.has(thread)) {
  this.readerGranted.delete(thread);
  this.activeReaders += 1;
  return true;
}
If no writer is active or waiting, reader can enter:
const writerWaiting = this.writerQueue.length > 0;
if (!this.writerActive && !writerWaiting) {
  this.activeReaders += 1;
  return true;
}
Note: Writer priority prevents reader starvation of writers
Block reader and add to reader queue:
thread.state = "blocked";
thread.blockedBy = this;
if (!this.readerQueue.includes(thread)) {
  this.readerQueue.push(thread);
}
return false;

Enter Write Operation

The enterWrite() method implements writer entry logic:
If monitor granted permission, enter immediately:
if (this.writerGranted === thread) {
  this.writerGranted = null;
  this.writerActive = true;
  return true;
}
If no readers and no other writer, enter:
if (!this.writerActive && this.activeReaders === 0) {
  this.writerActive = true;
  return true;
}
Block writer and add to writer queue:
thread.state = "blocked";
thread.blockedBy = this;
if (!this.writerQueue.includes(thread)) {
  this.writerQueue.push(thread);
}
return false;

Wake-up Policy

The wakeUpThreads() method implements the monitor’s scheduling policy:
if (!this.writerActive && this.activeReaders === 0 && 
    this.writerQueue.length > 0) {
  const nextWriter = this.writerQueue.shift();
  nextWriter.state = "ready";
  nextWriter.blockedBy = null;
  this.writerGranted = nextWriter;
  awakened.push(nextWriter);
  return awakened;
}
Policy: Prioritize writers over readers to prevent writer starvation. When a writer finishes:
  1. If writers waiting, wake one writer
  2. If no writers waiting, wake ALL readers
Why wake all readers? Multiple readers can proceed simultaneously, so we wake all of them at once. Only one writer can proceed, so we wake just one.

Permission Grant System

The monitor uses a two-phase grant system:
1

Grant Phase

When waking threads, monitor grants them permission by adding to readerGranted set or setting writerGranted
2

Consumption Phase

When thread re-executes enter method, it checks for pre-granted permission and consumes it
This prevents threads from re-blocking when they’re re-scheduled:
// Thread was blocked...
// Monitor wakes it: readerGranted.add(thread)
// Thread becomes ready, eventually runs again...
// Thread re-calls enterRead()

if (this.readerGranted.has(thread)) {
  this.readerGranted.delete(thread); // Consume permission
  this.activeReaders += 1;
  return true; // Don't block again!
}

Usage Example: Library Scenario

The library scenario demonstrates the monitor in action:
import { LibraryMonitor } from "../core/monitorLibrary.js";
import { Thread } from "../core/thread.js";
import { Instructions } from "../core/instructions.js";

const library = {
  monitor: new LibraryMonitor(),
  catalogVersion: 1,
  totalReads: 0,
  totalWrites: 0,
};

// Create reader threads (students)
for (let i = 1; i <= readerCount; i++) {
  const readInstructions = [
    { type: Instructions.ENTER_READ },
    { type: Instructions.READ_BOOK, title: `Libro-${i}` },
    { type: Instructions.EXIT_READ },
    { type: Instructions.END },
  ];
  const reader = new Thread(`Estudiante-${i}`, readInstructions);
  reader.role = "reader";
  engine.addThread(reader);
}

// Create writer thread (librarian)
const writerInstructions = [];
for (let i = 0; i < writerUpdates; i++) {
  writerInstructions.push(
    { type: Instructions.ENTER_WRITE },
    { type: Instructions.UPDATE_CATALOG },
    { type: Instructions.EXIT_WRITE }
  );
}
writerInstructions.push({ type: Instructions.END });

const writer = new Thread("Bibliotecario", writerInstructions);
writer.role = "writer";
engine.addThread(writer);

State Transitions

Let’s trace the monitor state with 3 readers (R1, R2, R3) and 1 writer (W):

FIFO Queue Management

The monitor maintains separate FIFO queues:
// Reader queue
this.readerQueue = [];
this.readerQueue.push(thread);    // Add to back
const reader = this.readerQueue.shift(); // Remove from front

// Writer queue
this.writerQueue = [];
this.writerQueue.push(thread);    // Add to back
const writer = this.writerQueue.shift(); // Remove from front
This ensures:
  • Fairness within role: Readers are served in arrival order, writers in arrival order
  • Priority between roles: Writers get priority over new readers
  • No starvation: Every waiting thread eventually proceeds

Monitor Invariants

The monitor maintains these invariants:
1

Mutual Exclusion

writerActive => activeReaders == 0 (writer excludes all readers)
2

Writer Exclusion

activeReaders > 0 => !writerActive (readers exclude writer)
3

Single Writer

At most one writer can be active at any time
4

Queue Consistency

Threads in queues are marked as blocked

Common Pitfalls

Writer Starvation: If readers keep arriving, writers may never get a chance. This implementation prevents it by blocking new readers when a writer is waiting.
Reader Starvation: If writers keep arriving, readers may wait forever. This implementation prevents it by waking ALL readers once the writer queue is empty.
Forgotten Exit: If a thread enters but never exits, it can block others forever:
monitor.enterRead();
// ... do work ...
// Forgot monitor.exitRead()!

Monitor vs Manual Synchronization

// Manual coordination with mutex + condition variables
mutex.acquire();
while (writerActive || writerWaiting) {
  readerCV.wait();
}
activeReaders++;
mutex.release();

// ... do reading ...

mutex.acquire();
activeReaders--;
if (activeReaders === 0) {
  writerCV.signal();
}
mutex.release();
Problems:
  • Easy to forget mutex acquire/release
  • Must manually manage all condition checks
  • Complex wake-up logic scattered across code
// Monitor encapsulates all synchronization
monitor.enterRead(thread);
// ... do reading ...
monitor.exitRead();
Benefits:
  • Mutex management implicit
  • All synchronization logic in one place
  • Harder to make mistakes
  • Clearer semantics

Visualizing Monitor Behavior

In the simulator, you can observe:
  • Active readers count: How many students are reading
  • Writer status: Whether librarian is updating catalog
  • Reader queue: Students waiting to read
  • Writer queue: Librarian waiting to write
  • Wake-up events: When threads transition from blocked to ready

Library (Readers-Writers)

Students reading books while librarian updates catalog - demonstrates monitor synchronization

Key Takeaways

1

High-level Abstraction

Monitors encapsulate mutex + condition variables in a clean interface
2

Implicit Mutual Exclusion

Monitor methods automatically enforce mutual exclusion
3

Separate Queues

Different condition queues for different roles (readers vs writers)
4

Custom Policy

Wake-up policy implements application-specific fairness rules
5

Permission Grant

Two-phase grant system prevents re-blocking of awakened threads

See Also