A Condition Variable is a synchronization primitive that allows threads to wait for a specific condition to become true. Unlike mutexes and semaphores that control access to resources, condition variables enable threads to signal each other when important events occur.
If condition not met, call wait() to suspend execution
3
Modify State
Another thread changes the shared state
4
Signal Change
Modifying thread calls signal() to wake a waiter
5
Re-check Condition
Awakened thread re-checks condition before proceeding
Always used with mutex: Condition variables are almost always paired with a mutex to protect the condition being checked. The mutex is released during wait and re-acquired before returning.
Here’s the complete condition variable implementation from the simulator:
source/js/core/conditionVariable.js
// Variable de condicion basica con cola FIFO.export class ConditionVariable { constructor(name = "Condition") { // Nombre para debug y para mostrar en mensajes si hace falta. this.name = name; // Cola de hilos que hicieron wait y quedaron dormidos. this.queue = []; } // Agrega un hilo a la cola de espera de la condicion. wait(thread) { // Evito duplicados por seguridad si el hilo intenta esperar otra vez. if (!this.queue.includes(thread)) this.queue.push(thread); } // Despierta a un hilo (el mas antiguo) si existe. signal() { if (this.queue.length === 0) return null; return this.queue.shift(); }}
The wait() method adds a thread to the waiting queue:
wait(thread) { // Prevent duplicates if thread tries to wait again if (!this.queue.includes(thread)) { this.queue.push(thread); }}
Why check for duplicates?
If a thread is already waiting and attempts to wait again (e.g., due to re-execution in the simulator), we avoid adding it twice. This maintains queue integrity and prevents the thread from being signaled multiple times.
Key characteristics:
Non-blocking: The method itself doesn’t block the thread (the caller does)
FIFO ordering: Uses push() to add to the back of the queue
Simple state: Just tracks which threads are waiting
signal() { if (this.queue.length === 0) return null; return this.queue.shift();}
Behavior:
Returns null if no threads are waiting (no-op)
Removes and returns the thread at the front of the queue
FIFO: The longest-waiting thread is awakened first
Signal vs Broadcast: This implementation provides signal() which wakes one thread. Some condition variable implementations also provide broadcast() which wakes all waiting threads. In the simulator, broadcast behavior is achieved by repeatedly calling signal().
The standard pattern for using condition variables:
// Waiting threadmutex.acquire();while (!condition) { condVar.wait(thread); // Note: In full implementation, wait() would: // 1. Release mutex // 2. Block thread // 3. Re-acquire mutex when signaled}// Condition is true, do workmutex.release();// Signaling threadmutex.acquire();// Modify shared statecondition = true;const thread = condVar.signal();if (thread) { thread.state = "ready";}mutex.release();
Always use while, not if: Check the condition in a loop, not just once. When a thread is awakened, the condition might have changed again due to other threads.