Skip to main content

Overview

The Printer Control scenario simulates a print server managing multiple print jobs competing for a limited number of available printers. This demonstrates the use of counting semaphores to manage access to multiple identical resources.

Real-World Problem

Consider an office with a shared print server:
  • 10 users submit print jobs
  • Only 3 printers are available
  • Jobs must wait if all printers are busy
  • When a printer finishes, the next job in queue can start
This is a classic resource pooling problem where we have N resources (printers) and M consumers (jobs) where M > N.

Shared Resources

The shared resources are multiple printer objects (a fixed pool). Each printer tracks its current owner, completed jobs, total pages printed, and status.
Protected by: Counting Semaphore (initialized to the number of available printers)

Synchronization Algorithm

This scenario uses a Counting Semaphore to manage the printer pool:
1

Wait for Available Printer

Print job executes WAIT on the semaphore. If count > 0, it decrements and proceeds. If count = 0, it blocks until a printer is released.
2

Acquire and Print

Job is assigned to an available printer and begins printing its pages
3

Signal Completion

Job executes SIGNAL on the semaphore, incrementing the count and waking up one waiting job

Scenario Setup

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

Configuration Options

ParameterDescriptionDefault
jobCountNumber of print jobs (threads)Varies
printerCountNumber of available printersMinimum 1
jobsArray of job configurations with pages1 page per job

Example Execution Flow

Printers Available: 2
Semaphore Count: 2

Time 0:
  Job-1: WAIT_SEM (count: 2→1) → Printer-1
  Job-2: WAIT_SEM (count: 1→0) → Printer-2
  Job-3: WAIT_SEM (count: 0) → BLOCKED
  Job-4: WAIT_SEM (count: 0) → BLOCKED
  Job-5: WAIT_SEM (count: 0) → BLOCKED

Time 5:
  Job-1: PRINT complete → SIGNAL_SEM (count: 0→1)
  Job-3: UNBLOCKED → Printer-1

Time 8:
  Job-2: PRINT complete → SIGNAL_SEM (count: 0→1)
  Job-4: UNBLOCKED → Printer-2

Time 12:
  Job-3: PRINT complete → SIGNAL_SEM (count: 0→1)
  Job-5: UNBLOCKED → Printer-1

Thread Instructions

Each print job thread executes:
  1. WAIT_SEM - Request a printer from the pool (blocks if none available)
  2. PRINT - Print the specified number of pages
  3. SIGNAL_SEM - Release the printer back to the pool
  4. END - Terminate thread
The semaphore count represents the number of available printers. When it reaches 0, new jobs must wait. Each SIGNAL operation makes one printer available for the next waiting job.

Semaphore vs Mutex

AspectMutexCounting Semaphore
Resources1 (binary)N (counting)
Use CaseMutual exclusionResource pooling
Initial ValueUnlockedNumber of resources
ExampleBank accountPrinter pool

Key Learning Points

  • Counting Semaphores: Track multiple identical resources
  • Resource Pool: Jobs share access to N printers
  • Blocking: Jobs wait when all printers are busy
  • Fairness: Jobs typically served in FIFO order (implementation-dependent)
  • Statistics: Each printer tracks its usage metrics independently