Skip to main content

Overview

Scenarios are pre-configured simulations that demonstrate specific synchronization concepts. Each scenario sets up threads with instruction sequences and creates the shared resources they interact with.

Scenario Structure

All scenarios follow a consistent pattern:
  1. Create synchronization primitives (mutex, semaphore, etc.)
  2. Initialize shared state (account balance, resource counts, etc.)
  3. Create threads with specific instruction sequences
  4. Register threads with the engine
  5. Return context object containing all shared resources

Available Scenarios

The simulator includes seven scenarios demonstrating different synchronization concepts:

Bank

Concept: Mutual exclusion with mutexFile: bankScenario.jsDemonstrates: Critical section protection, race conditions, account consistency

Printer

Concept: Resource pooling with semaphoreFile: printerScenario.jsDemonstrates: Counting semaphore, limited resource allocation, job queuing

Restaurant

Concept: Producer-consumer with condition variableFile: restaurantScenario.jsDemonstrates: Wait/signal pattern, blocking and waking threads

Library

Concept: Readers-writers with monitorFile: libraryScenario.jsDemonstrates: Concurrent reads, exclusive writes, monitor pattern

Race

Concept: Synchronization barrierFile: raceBarrierScenario.jsDemonstrates: Barrier synchronization, checkpoint waiting

House

Concept: Thread join and awaitFile: houseScenario.jsDemonstrates: Task dependencies, join/await operations

Peterson

Concept: Peterson’s mutual exclusion algorithmFile: petersonScenario.jsDemonstrates: Software-only mutual exclusion for two threads

Scenario Deep Dive

Bank Scenario

Demonstrates mutex-based protection of a shared bank account. Source Code:
bankScenario.js
import { Thread } from "../core/thread.js";
import { Instructions } from "../core/instructions.js";
import { Mutex } from "../core/mutex.js";

export function createBankScenario(
  engine,
  threadCount,
  operations,
  initialBalance,
) {
  const mutex = new Mutex();
  const account = { balance: initialBalance };

  for (let i = 1; i <= threadCount; i++) {
    const operation = operations[i - 1] ?? {
      type: Instructions.WITHDRAW,
      amount: 100,
    };
    const amount = Math.max(0, Number(operation.amount) || 0);

    const instructions = [
      { type: Instructions.ACQUIRE },
      { type: operation.type, amount },
      { type: Instructions.RELEASE },
      { type: Instructions.END },
    ];

    const thread = new Thread(`Cliente-${i}`, instructions);
    engine.addThread(thread);
  }

  return { mutex, account };
}
Parameters:
  • threadCount - Number of client threads
  • operations - Array of operations (WITHDRAW/DEPOSIT with amounts)
  • initialBalance - Starting account balance
Context:
{
  mutex: Mutex,
  account: { balance: number }
}
[
  { type: Instructions.ACQUIRE },           // Enter critical section
  { type: Instructions.WITHDRAW, amount: 100 },  // Modify shared state
  { type: Instructions.RELEASE },           // Exit critical section
  { type: Instructions.END }
]

Printer Scenario

Demonstrates semaphore-based resource pooling with multiple printers. Source Code:
printerScenario.js
import { Thread } from "../core/thread.js";
import { Semaphore } from "../core/semaphore.js";
import { Instructions } from "../core/instructions.js";

export function createPrinterScenario(engine, jobCount, printerCount, jobs) {
  const safePrinters = Math.max(1, Number(printerCount) || 1);
  const semaphore = new Semaphore(safePrinters);
  const printers = Array.from({ length: safePrinters }, (_, i) => ({
    id: i + 1,
    owner: null,
    completedJobs: 0,
    totalPages: 0,
    lastCompletedJob: null,
  }));

  for (let i = 1; i <= jobCount; i++) {
    const pages = Math.max(1, Number(jobs[i - 1]?.pages) || 1);
    const instructions = [
      { type: Instructions.WAIT_SEM },
      { type: Instructions.PRINT, pages },
      { type: Instructions.SIGNAL_SEM },
      { type: Instructions.END },
    ];

    const thread = new Thread(`Trabajo-${i}`, instructions);
    thread.jobPages = pages;
    engine.addThread(thread);
  }

  return { semaphore, printers };
}
Parameters:
  • jobCount - Number of print jobs
  • printerCount - Number of available printers
  • jobs - Array of job configurations with pages
Context:
{
  semaphore: Semaphore,
  printers: Array<{
    id: number,
    owner: Thread | null,
    completedJobs: number,
    totalPages: number,
    lastCompletedJob: { name: string, pages: number } | null
  }>
}
The semaphore capacity matches the number of printers, limiting concurrent access to available resources.

Restaurant Scenario

Demonstrates producer-consumer pattern with condition variables. Source Code:
restaurantScenario.js
import { Thread } from "../core/thread.js";
import { Instructions } from "../core/instructions.js";
import { ConditionVariable } from "../core/conditionVariable.js";

export function createRestaurantScenario(engine, customerCount, mealsToCook) {
  const safeCustomers = Math.max(1, Number(customerCount) || 1);
  const safeMeals = Math.max(1, Number(mealsToCook) || 1);

  const restaurant = {
    availableDishes: 0,
    totalCooked: 0,
    totalEaten: 0,
    mealsTarget: safeMeals,
    foodCondition: new ConditionVariable("ComidaLista"),
  };

  for (let i = 1; i <= safeCustomers; i++) {
    const customerInstructions = [
      { type: Instructions.WAIT_FOOD },
      { type: Instructions.EAT_FOOD },
      { type: Instructions.END },
    ];

    const customer = new Thread(`Cliente-${i}`, customerInstructions);
    engine.addThread(customer);
  }

  const chefInstructions = [];
  for (let i = 0; i < safeMeals; i++) {
    chefInstructions.push({ type: Instructions.COOK_DISH });
    chefInstructions.push({ type: Instructions.SIGNAL_FOOD });
  }
  chefInstructions.push({ type: Instructions.END });

  const chefThread = new Thread("Chef", chefInstructions);
  engine.addThread(chefThread);

  return { restaurant, chefThread };
}
Parameters:
  • customerCount - Number of customer threads
  • mealsToCook - Number of meals the chef will prepare
Context:
{
  restaurant: {
    availableDishes: number,
    totalCooked: number,
    totalEaten: number,
    mealsTarget: number,
    foodCondition: ConditionVariable
  },
  chefThread: Thread
}
Customer Thread:
[
  { type: Instructions.WAIT_FOOD },  // Block until food available
  { type: Instructions.EAT_FOOD },   // Consume food
  { type: Instructions.END }
]
Chef Thread:
[
  { type: Instructions.COOK_DISH },   // Prepare dish
  { type: Instructions.SIGNAL_FOOD }, // Wake waiting customer
  // ... repeat for each meal
  { type: Instructions.END }
]

Library Scenario

Demonstrates readers-writers problem with monitor pattern. Source Code:
libraryScenario.js
import { Thread } from "../core/thread.js";
import { Instructions } from "../core/instructions.js";
import { LibraryMonitor } from "../core/monitorLibrary.js";

export function createLibraryScenario(engine, readerCount, writerUpdates) {
  const safeReaders = Math.max(1, Number(readerCount) || 1);
  const safeUpdates = Math.max(1, Number(writerUpdates) || 1);

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

  for (let i = 1; i <= safeReaders; 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";
    reader.targetBook = `Libro-${i}`;
    engine.addThread(reader);
  }

  const writerInstructions = [];
  for (let i = 0; i < safeUpdates; i++) {
    writerInstructions.push({ type: Instructions.ENTER_WRITE });
    writerInstructions.push({ type: Instructions.UPDATE_CATALOG });
    writerInstructions.push({ type: Instructions.EXIT_WRITE });
  }
  writerInstructions.push({ type: Instructions.END });

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

  return { library, writerThread: writer };
}
Parameters:
  • readerCount - Number of student reader threads
  • writerUpdates - Number of catalog updates by librarian
Multiple readers can access the library simultaneously, but writers require exclusive access.

Race Barrier Scenario

Demonstrates synchronization barrier at a checkpoint. Source Code:
raceBarrierScenario.js
import { Thread } from "../core/thread.js";
import { Instructions } from "../core/instructions.js";
import { Barrier } from "../core/barrier.js";

export function createRaceBarrierScenario(engine, racerCount) {
  const safeRacers = Math.max(1, Number(racerCount) || 1);
  const barrier = new Barrier(safeRacers);

  const race = {
    barrier,
    totalRacers: safeRacers,
    passedCheckpointCount: 0,
    finishedCount: 0,
  };

  for (let i = 1; i <= safeRacers; i++) {
    const instructions = [
      { type: Instructions.RUN_STAGE, stage: "to-checkpoint" },
      { type: Instructions.BARRIER_WAIT },
      { type: Instructions.RUN_STAGE, stage: "to-finish" },
      { type: Instructions.END },
    ];

    const racer = new Thread(`Corredor-${i}`, instructions);
    racer.role = "racer";
    racer.passedCheckpoint = false;
    racer.finishedRace = false;
    engine.addThread(racer);
  }

  return { race };
}
Parameters:
  • racerCount - Number of racers (must all reach checkpoint before any continue)
Context:
{
  race: {
    barrier: Barrier,
    totalRacers: number,
    passedCheckpointCount: number,
    finishedCount: number
  }
}
All racers must reach the barrier before any can proceed - this demonstrates synchronization points in parallel algorithms.

House Scenario

Demonstrates thread join and await for task dependencies. Source Code:
houseScenario.js
import { Thread } from "../core/thread.js";
import { Instructions } from "../core/instructions.js";
import { JoinManager } from "../core/joinManager.js";

export function createHouseScenario(engine, durations) {
  const d = {
    foundation: Math.max(1, Number(durations.foundation) || 1),
    walls: Math.max(1, Number(durations.walls) || 1),
    roof: Math.max(1, Number(durations.roof) || 1),
    installations: Math.max(1, Number(durations.installations) || 1),
  };

  const house = {
    delivered: false,
    stages: {
      Cimientos: { current: 0, total: d.foundation, owner: "Maestro-Cimientos", done: false },
      Paredes: { current: 0, total: d.walls, owner: "Maestro-Paredes", done: false },
      Techo: { current: 0, total: d.roof, owner: "Techador", done: false },
      Instalaciones: { current: 0, total: d.installations, owner: "Instalador", done: false },
    },
  };

  const joinManager = new JoinManager();

  // Foundation worker starts immediately
  const foundationWorker = new Thread("Maestro-Cimientos", [
    { type: Instructions.BUILD_STAGE, stage: "Cimientos", duration: d.foundation },
    { type: Instructions.END },
  ]);

  // Wall worker waits for foundation
  const wallWorker = new Thread("Maestro-Paredes", [
    { type: Instructions.JOIN_THREAD, target: "Maestro-Cimientos" },
    { type: Instructions.BUILD_STAGE, stage: "Paredes", duration: d.walls },
    { type: Instructions.END },
  ]);

  // Roof worker waits for walls
  const roofWorker = new Thread("Techador", [
    { type: Instructions.JOIN_THREAD, target: "Maestro-Paredes" },
    { type: Instructions.BUILD_STAGE, stage: "Techo", duration: d.roof },
    { type: Instructions.END },
  ]);

  // Installation worker waits for walls
  const installationWorker = new Thread("Instalador", [
    { type: Instructions.JOIN_THREAD, target: "Maestro-Paredes" },
    { type: Instructions.BUILD_STAGE, stage: "Instalaciones", duration: d.installations },
    { type: Instructions.END },
  ]);

  // Architect waits for roof and installations
  const architect = new Thread("Arquitecto", [
    { type: Instructions.AWAIT_ALL, targets: ["Techador", "Instalador"] },
    { type: Instructions.COMPLETE_HOUSE },
    { type: Instructions.END },
  ]);

  engine.addThread(foundationWorker);
  engine.addThread(wallWorker);
  engine.addThread(roofWorker);
  engine.addThread(installationWorker);
  engine.addThread(architect);

  return { house, joinManager, architectThread: architect };
}
Parameters:
  • durations - Object with duration for each stage (foundation, walls, roof, installations)
Dependency Graph:
This scenario demonstrates how complex task dependencies can be modeled using JOIN_THREAD and AWAIT_ALL instructions.

Peterson Scenario

Demonstrates Peterson’s mutual exclusion algorithm for two threads. Source Code:
petersonScenario.js
import { Thread } from "../core/thread.js";
import { Instructions } from "../core/instructions.js";
import { PetersonLock } from "../core/petersonLock.js";

export function createPetersonScenario(engine, cyclesPerRobot) {
  const cycles = Math.max(1, Number(cyclesPerRobot) || 1);
  const lock = new PetersonLock();

  const station = {
    lock,
    usageCount: [0, 0],
    totalUses: 0,
  };

  for (let i = 0; i < 2; i++) {
    const instructions = [];
    for (let c = 0; c < cycles; c++) {
      instructions.push({ type: Instructions.PETERSON_LOCK });
      instructions.push({ type: Instructions.USE_STATION, cycle: c + 1 });
      instructions.push({ type: Instructions.PETERSON_UNLOCK });
    }
    instructions.push({ type: Instructions.END });

    const robot = new Thread(`Robot-${i + 1}`, instructions);
    robot.petersonId = i;
    engine.addThread(robot);
  }

  return { station };
}
Parameters:
  • cyclesPerRobot - Number of times each robot uses the shared station
Peterson’s algorithm only works correctly with exactly 2 threads. Each thread must have a unique petersonId (0 or 1).

Configuring Scenarios

Scenarios accept configuration parameters that control:
  • threadCount (Bank)
  • jobCount (Printer)
  • customerCount (Restaurant)
  • readerCount (Library)
  • racerCount (Race)
  • Fixed at 5 workers + 1 architect (House)
  • Fixed at 2 robots (Peterson)
  • initialBalance (Bank)
  • printerCount (Printer)
  • mealsToCook (Restaurant)
  • No explicit limit (Library - monitor handles coordination)
  • totalRacers (Race - for barrier size)
  • operations array with type and amount (Bank)
  • jobs array with pages (Printer)
  • writerUpdates (Library)
  • durations object for stages (House)
  • cyclesPerRobot (Peterson)

Scenario-Algorithm Relationship

Each scenario demonstrates a specific synchronization algorithm:
ScenarioAlgorithmKey Concept
BankMutexMutual exclusion with locks
PrinterCounting SemaphoreResource pool management
RestaurantCondition VariableProducer-consumer coordination
LibraryMonitor (Readers-Writers)Concurrent reads, exclusive writes
RaceBarrierSynchronization points
HouseJoin/AwaitTask dependency graphs
PetersonPeterson’s AlgorithmSoftware-only mutual exclusion

Creating Custom Scenarios

To create a new scenario:
1

Create Scenario File

Create a new file in source/js/scenarios/:
myScenario.js
import { Thread } from "../core/thread.js";
import { Instructions } from "../core/instructions.js";

export function createMyScenario(engine, params) {
  // Scenario implementation
}
2

Initialize Synchronization Primitives

const mutex = new Mutex();
const semaphore = new Semaphore(5);
// etc.
3

Create Shared State

const sharedState = {
  counter: 0,
  items: [],
  // etc.
};
4

Define Thread Instructions

const instructions = [
  { type: Instructions.ACQUIRE },
  { type: Instructions.DEPOSIT, amount: 50 },
  { type: Instructions.RELEASE },
  { type: Instructions.END }
];
5

Create and Register Threads

for (let i = 1; i <= threadCount; i++) {
  const thread = new Thread(`Thread-${i}`, instructions);
  engine.addThread(thread);
}
6

Return Context

return { mutex, sharedState };

Next Steps

Threads

Learn about the Thread model and states

Instruction Set

Explore all available instructions