🧪 Lab 2 — Techniques for Thread Synchronization
Duration: ~1 hour
Audience: Undergraduate students familiar with basic threading (Lab 1)
Languages: C (POSIX Threads), optional Python demos
Environment: Linux / Windows (WSL recommended)
🎯 Learning Objectives
By the end of this lab, students will be able to:
- Use mutexes, condition variables, and barriers to coordinate threads.
- Identify and prevent race conditions and deadlocks.
- Understand how condition variables work internally.
- Apply synchronization techniques to real-world “smart environment” scenarios
 such as smart farming, smart homes, and smart buildings.
🧠 Concept Recap
| Primitive | Purpose | Example Scenario | 
|---|---|---|
| Mutex | Protect shared data | Shared bank account, temperature log | 
| Condition Variable | Wait for a condition to be met | Producer–consumer, smart sensors | 
| Barrier | Wait for all threads to reach a point | Periodic data aggregation | 
| Atomic Ops | Fast counter / flag updates | Counting detected motion events | 
🧩 Understanding Condition Variables
A condition variable lets threads wait for a condition to become true
while temporarily releasing a lock (mutex) so that other threads can change that condition.
Think of it as:
“If the fridge is empty, the consumer waits.
When the producer refills it, it notifies the consumer.”
⚙️ The Three Key Operations
| Function | Description | 
|---|---|
| pthread_cond_wait(&cond, &mutex) | Waits for a condition. Temporarily releases the mutex while sleeping, and reacquires it before returning. | 
| pthread_cond_signal(&cond) | Wakes up one waiting thread. | 
| pthread_cond_broadcast(&cond) | Wakes up all waiting threads. | 
💡 Typical Pattern
pthread_mutex_lock(&mtx);
while (!condition)
    pthread_cond_wait(&cond, &mtx);
// perform work once condition true
pthread_mutex_unlock(&mtx);
🔁 Why the while loop?
Even after being signaled, a waiting thread must re-check the condition, because:
- Another thread may have modified it before this one resumed.
- Some systems allow spurious wakeups (wakeups without signals).
Hence we use:
while (condition_not_true)
    pthread_cond_wait(&cond, &mutex);
🧩 Mini Example - cond_example.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int data_ready = 0;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  c = PTHREAD_COND_INITIALIZER;
void* producer(void* arg) {
    sleep(1);
    pthread_mutex_lock(&m);
    data_ready = 1;
    printf("Producer: data is ready!\n");
    pthread_cond_signal(&c);
    pthread_mutex_unlock(&m);
    return NULL;
}
void* consumer(void* arg) {
    pthread_mutex_lock(&m);
    while (!data_ready) {
        printf("Consumer: waiting...\n");
        pthread_cond_wait(&c, &m);
    }
    printf("Consumer: got the data!\n");
    pthread_mutex_unlock(&m);
    return NULL;
}
int main() {
    pthread_t prod, cons;
    pthread_create(&cons, NULL, consumer, NULL);
    pthread_create(&prod, NULL, producer, NULL);
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);
}
Build & Run
gcc -pthread -o cond_example cond_example.c
./cond_example
Output
Consumer: waiting...
Producer: data is ready!
Consumer: got the data!
✅ The consumer waits efficiently — it’s not busy-waiting.
🧩 Part 1 — Smart Farm Sensor Fusion (using a Barrier)
🌾 Scenario
A smart farm has multiple sensors (temperature, humidity, soil moisture). Each thread reads data from one sensor. After all sensors report, the system fuses the data to make an irrigation decision.
🧰 Code — smartfarm_barrier.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define NUM_SENSORS 3
typedef struct {
    int id;   // sensor id
} SensorArg;
pthread_barrier_t barrier;
int readings[NUM_SENSORS];
void* sensor(void* arg) {
    SensorArg* a = (SensorArg*)arg;
    int id = a->id;
    // Simulate variable reading time
    sleep(1 + id);
    readings[id] = 20 + id * 5;  // fake data - we could use rand here with
                                // To get new random readings on each run.
    printf("Sensor %d reports %d C\n", id, readings[id]);
    // Wait for all sensors to finish their reading
    pthread_barrier_wait(&barrier);
    // One thread (e.g., id 0) performs fusion/decision after the barrier
    if (id == 0) {
        int sum = 0;
        for (int i = 0; i < NUM_SENSORS; i++) sum += readings[i];
        int avg = sum / NUM_SENSORS;
        printf("Fusion thread: average temp = %d C. Output: %s\n",
               avg, (avg > 25 ? "ON" : "OFF"));
    }
    return NULL;
}
int main(void) {
    pthread_t threads[NUM_SENSORS];
    SensorArg args[NUM_SENSORS];
    pthread_barrier_init(&barrier, NULL, NUM_SENSORS);
    for (int i = 0; i < NUM_SENSORS; i++) {
        args[i].id = i;
        pthread_create(&threads[i], NULL, sensor, &args[i]);
    }
    for (int i = 0; i < NUM_SENSORS; i++) {
        pthread_join(threads[i], NULL);
    }
    pthread_barrier_destroy(&barrier);
    return 0;
}
Build & Run:
gcc -pthread -o smartfarm_barrier smartfarm_barrier.c
./smartfarm_barrier
Discussion
What happens if the barrier is removed?
🧩 Part 2 — Smart Home Producer–Consumer (using Condition Variables)
🏠 Scenario
A smart-home gateway receives sensor events (producer) and processes them (consumer). We’ll implement a simple bounded buffer using a mutex and condition variables.
🧰 Code — smart_home_pc.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define BUFFER_SIZE 5
#define NUM_EVENTS 20
int buffer[BUFFER_SIZE];
int count = 0, in = 0, out = 0;
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_full  = PTHREAD_COND_INITIALIZER;
void* producer(void* arg) {
    for (int i = 0; i < NUM_EVENTS; i++) {
        pthread_mutex_lock(&mtx);
        while (count == BUFFER_SIZE)
            pthread_cond_wait(¬_full, &mtx);
        buffer[in] = i;
        in = (in + 1) % BUFFER_SIZE;
        count++;
        printf("[Producer] Sensor event %d produced (count=%d)\n", i, count);
        pthread_cond_signal(¬_empty);
        pthread_mutex_unlock(&mtx);
        usleep(100000);
    }
    return NULL;
}
void* consumer(void* arg) {
    for (int i = 0; i < NUM_EVENTS; i++) {
        pthread_mutex_lock(&mtx);
        while (count == 0)
            pthread_cond_wait(¬_empty, &mtx);
        int item = buffer[out];
        out = (out + 1) % BUFFER_SIZE;
        count--;
        printf("              [Consumer] processed event %d (count=%d)\n", item, count);
        pthread_cond_signal(¬_full);
        pthread_mutex_unlock(&mtx);
        usleep(150000);
    }
    return NULL;
}
int main() {
    pthread_t prod, cons;
    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);
}
Build & Run:
gcc -pthread -o smart_home_pc smart_home_pc.c
./smart_home_pc
Output
One of the possible outputs.
PS D:\UOP\Course\lab\lab2> ./smart_home
[Producer] Sensor event 0 produced (count=1)
              [Consumer] processed event 0 (count=0)
[Producer] Sensor event 1 produced (count=1)
              [Consumer] processed event 1 (count=0)
[Producer] Sensor event 2 produced (count=1)
[Producer] Sensor event 3 produced (count=2)
              [Consumer] processed event 2 (count=1)
[Producer] Sensor event 4 produced (count=2)
              [Consumer] processed event 3 (count=1)
[Producer] Sensor event 5 produced (count=2)
[Producer] Sensor event 6 produced (count=3)
              [Consumer] processed event 4 (count=2)
[Producer] Sensor event 7 produced (count=3)
              [Consumer] processed event 5 (count=2)
[Producer] Sensor event 8 produced (count=3)
              [Consumer] processed event 6 (count=2)
[Producer] Sensor event 9 produced (count=3)
[Producer] Sensor event 10 produced (count=4)
              [Consumer] processed event 7 (count=3)
[Producer] Sensor event 11 produced (count=4)
              [Consumer] processed event 8 (count=3)
[Producer] Sensor event 12 produced (count=4)
[Producer] Sensor event 13 produced (count=5)
              [Consumer] processed event 9 (count=4)
[Producer] Sensor event 14 produced (count=5)
              [Consumer] processed event 10 (count=4)
[Producer] Sensor event 15 produced (count=5)
              [Consumer] processed event 11 (count=4)
[Producer] Sensor event 16 produced (count=5)
              [Consumer] processed event 12 (count=4)
[Producer] Sensor event 17 produced (count=5)
              [Consumer] processed event 13 (count=4)
[Producer] Sensor event 18 produced (count=5)
              [Consumer] processed event 14 (count=4)
[Producer] Sensor event 19 produced (count=5)
              [Consumer] processed event 15 (count=4)
              [Consumer] processed event 16 (count=3)
              [Consumer] processed event 17 (count=2)
              [Consumer] processed event 18 (count=1)
              [Consumer] processed event 19 (count=0)
🧩 Part 3 — Smart Greenhouse: Producer, Consumer and Monitor
🌿 Scenario
A smart greenhouse has multiple temperature sensors (producers) generating readings, actuators (consumers) adjusting conditions, and a monitor thread that periodically checks the latest temperature to detect unsafe trends.
🧰 Code — smart_greenhouse.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
// ============================ Config =============================
#define BUFFER_SIZE    5
#define NUM_READINGS   5
#define SAFE_TEMP_MIN  18
#define SAFE_TEMP_MAX  28
// ANSI colors
#define RED    "\033[1;31m"   // Producer (Sensor)
#define BLUE   "\033[1;34m"   // Actuator (Consumer)
#define GREEN  "\033[1;32m"   // Monitor
#define RESET  "\033[0m"
// =========================== Commands ============================
typedef enum {
    CMD_NONE = 0,
    CMD_COOL_ON,
    CMD_HEAT_ON,
    CMD_STABLE,
    CMD_SHUTDOWN
} command_t;
// ======================= Shared: readings buf ====================
static int buffer[BUFFER_SIZE];
static int count = 0;
static int producer_done = 0;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  not_empty = PTHREAD_COND_INITIALIZER;
static pthread_cond_t  not_full  = PTHREAD_COND_INITIALIZER;
// ==================== Shared: monitor→actuator cmd ===============
static command_t cmd_mailbox = CMD_NONE;
static int cmd_has_msg = 0;
static pthread_mutex_t cmd_mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  cmd_cv  = PTHREAD_COND_INITIALIZER;
// ============================ Producer ===========================
static void* producer(void* arg) {
    int in_idx = 0;
    for (int i = 0; i < NUM_READINGS; i++) {
        int temp = 15 + rand() % 15; // [15..29]
        // push temp into ring buffer
        pthread_mutex_lock(&mtx);
        while (count == BUFFER_SIZE)
            pthread_cond_wait(¬_full, &mtx);
        buffer[in_idx] = temp;
        in_idx = (in_idx + 1) % BUFFER_SIZE;
        count++;
        pthread_cond_signal(¬_empty);
        pthread_mutex_unlock(&mtx);
        // Print outside lock
        printf(RED "[Sensor] Produced temp = %dC (count=%d)\n" RESET, temp, count);
        usleep(150000); //Modifying this will change the rate of how fast are genereated
    }
    // mark done and wake any waiters
    pthread_mutex_lock(&mtx);
    producer_done = 1;
    pthread_cond_broadcast(¬_empty);
    pthread_mutex_unlock(&mtx);
    return NULL;
}
// ============================ Monitor ============================
// Consumes readings from buffer, computes decision, signals actuator.
static void* monitor(void* arg) {
    
    int out_idx = 0;
    
    while (1) {
        int temp = 0;
        int done = 0;
        // Pop one reading (or exit if empty & producer_done)
        pthread_mutex_lock(&mtx);
        while (count == 0 && !producer_done)
            pthread_cond_wait(¬_empty, &mtx);
        if (count == 0 && producer_done) {
            done = 1;
        } else {
            temp = buffer[out_idx];
            out_idx = (out_idx + 1) % BUFFER_SIZE;
            count--;
            pthread_cond_signal(¬_full);
        }
        pthread_mutex_unlock(&mtx);
        if (done) break;
        // Decide command based on temp
        command_t cmd;
        if (temp > SAFE_TEMP_MAX)       cmd = CMD_COOL_ON;
        else if (temp < SAFE_TEMP_MIN)  cmd = CMD_HEAT_ON;
        else                            cmd = CMD_STABLE;
        // Announce decision (outside locks)
        if (cmd == CMD_COOL_ON)
            printf(GREEN "                          [Monitor] temp=%dC . COOL ON\n" RESET, temp);
        else if (cmd == CMD_HEAT_ON)
            printf(GREEN "                          [Monitor] temp=%dC . HEAT ON\n" RESET, temp);
        else
            printf(GREEN "                          [Monitor] temp=%dC . STABLE\n" RESET, temp);
        // Post command to actuator (single-message mailbox)
        pthread_mutex_lock(&cmd_mtx);
        while (cmd_has_msg) // wait until previous command is taken
            pthread_cond_wait(&cmd_cv, &cmd_mtx);
        cmd_mailbox = cmd;
        cmd_has_msg = 1;
        pthread_cond_signal(&cmd_cv);
        pthread_mutex_unlock(&cmd_mtx);
    }
    // Send shutdown command
    pthread_mutex_lock(&cmd_mtx);
    while (cmd_has_msg)
        pthread_cond_wait(&cmd_cv, &cmd_mtx);
    cmd_mailbox = CMD_SHUTDOWN;
    cmd_has_msg = 1;
    pthread_cond_signal(&cmd_cv);
    pthread_mutex_unlock(&cmd_mtx);
    return NULL;
}
// ============================ Actuator ===========================
// Waits for commands from Monitor and performs actions.
static void* actuator(void* arg) {
    (void)arg;
    while (1) {
        command_t cmd;
        // Wait for a command
        pthread_mutex_lock(&cmd_mtx);
        while (!cmd_has_msg)
            pthread_cond_wait(&cmd_cv, &cmd_mtx);
        cmd = cmd_mailbox;
        cmd_has_msg = 0;
        pthread_cond_signal(&cmd_cv); // let monitor post next
        pthread_mutex_unlock(&cmd_mtx);
        // Act on command
        if (cmd == CMD_SHUTDOWN) {
            printf(BLUE "             [Actuator] Shutdown received. Exiting.\n" RESET);
            break;
        } else if (cmd == CMD_COOL_ON) {
            printf(BLUE "             [Actuator] Cooling ON\n" RESET);
        } else if (cmd == CMD_HEAT_ON) {
            printf(BLUE "             [Actuator] Heating ON\n" RESET);
        } else if (cmd == CMD_STABLE) {
            printf(BLUE "             [Actuator] Systems stable\n" RESET);
        }
        usleep(200000);
    }
    return NULL;
}
// ============================== Main ============================
int main(void) {
    srand((unsigned)time(NULL));
    pthread_t prod_th, mon_th, act_th;
    pthread_create(&prod_th, NULL, producer, NULL);
    pthread_create(&mon_th,  NULL, monitor,  NULL);
    pthread_create(&act_th,  NULL, actuator, NULL);
    pthread_join(prod_th, NULL);
    pthread_join(mon_th,  NULL);
    pthread_join(act_th,  NULL);
    printf("\nMain: All threads finished. Exiting...\n");
    return 0;
}
Build & Run
gcc -pthread -o smart_greenhouse smart_greenhouse.c
./smart_greenhouse
Output
[Sensor] Produced temp = 26C (count=1)
[Monitor] temp=26C . STABLE
[Actuator] Systems stable
[Sensor] Produced temp = 17C (count=1)
[Monitor] temp=17C . HEAT ON
[Actuator] Heating ON
[Sensor] Produced temp = 19C (count=1)
[Monitor] temp=19C . STABLE
[Actuator] Systems stable
[Sensor] Produced temp = 25C (count=1)
[Monitor] temp=25C . STABLE
[Actuator] Systems stable
[Sensor] Produced temp = 29C (count=0)
[Monitor] temp=29C . COOL ON
[Actuator] Cooling ON
[Actuator] Shutdown received. Exiting.
Expected Behavior
- The sensor produces readings.
- The monitor consumes temperatures from the sensor buffer.
- The Monitor decides what to do (cool, heat, stable).
- Monitor signals the Actuator via a dedicated mailbox + condition variable, avoiding busy-waiting and minimizing lock contention.
- The actuator consumes perform the action.
🧠 Key Takeaways
- Synchronization ensures correctness but adds overhead.
- Choose the right primitive:
- 🧩 Mutex → exclusive access
- 🔄 Condition Variables → coordination between threads
- 🚧 Barriers → phase synchronization for groups of threads
 
- Real-world smart systems (smart homes, farms, factories) rely on these to avoid data races and inconsistent decisions.
🐍 Appendix — Python Versions of the Synchronization Experiments
🧰 Setup
# (Optional) create a virtual environment
python -m venv .venv
# Windows
Run this command before activating your venv:
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.venv\Scripts\activate
# Linux/macOS
source .venv/bin/activate
# Install colorama (for colored terminal output)
pip install colorama
If you don’t want colors, remove the colorama parts and the ANSI constants.
A) Smart Farm Sensor Fusion (Barrier)
Idea: Same as the C barrier example — multiple sensors wait at a barrier, and after all arrive, one thread fuses the results.
File: smartfarm_barrier.py
import threading
import time
from random import randint
from colorama import init, Fore, Style
init(autoreset=True)
NUM_SENSORS = 3
readings = [None] * NUM_SENSORS
barrier = threading.Barrier(NUM_SENSORS)
def sensor(sensor_id: int):
    time.sleep(0.5 + sensor_id * 0.4)
    readings[sensor_id] = 20 + sensor_id * 5
    print(f"{Fore.RED}[Sensor {sensor_id}] reports {readings[sensor_id]}C{Style.RESET_ALL}")
    barrier.wait()
    if sensor_id == 0:
        avg = sum(readings) // NUM_SENSORS
        decision = "ON" if avg > 25 else "OFF"
        print(f"{Fore.GREEN}Fusion: avg={avg}C -> irrigation {decision}{Style.RESET_ALL}")
def main():
    threads = [threading.Thread(target=sensor, args=(i,)) for i in range(NUM_SENSORS)]
    for t in threads: t.start()
    for t in threads: t.join()
if __name__ == "__main__":
    main()
Run:
python smartfarm_barrier.py
B) Smart Home Producer–Consumer (Queue / Condition)
Idea: Same bounded-buffer behavior as the C version.
In Python, queue.Queue(maxsize=N) already handles the condition variable logic internally.
File: smart_home_pc.py
import time
import threading
import queue
from colorama import init, Fore, Style
init(autoreset=True)
BUFFER_SIZE = 5
NUM_EVENTS = 20
q = queue.Queue(maxsize=BUFFER_SIZE)
def producer():
    for i in range(NUM_EVENTS):
        q.put(i)
        print(f"{Fore.RED}[Producer] Sensor event {i} produced (count={q.qsize()}){Style.RESET_ALL}")
        time.sleep(0.1)
def consumer():
    for _ in range(NUM_EVENTS):
        item = q.get()
        print(f"{Fore.BLUE}              [Consumer] processed event {item} (count={q.qsize()}){Style.RESET_ALL}")
        time.sleep(0.15)
        q.task_done()
def main():
    t_prod = threading.Thread(target=producer)
    t_cons = threading.Thread(target=consumer)
    t_prod.start(); t_cons.start()
    t_prod.join(); q.join(); t_cons.join()
if __name__ == "__main__":
    main()
Run:
python smart_home_pc.py
C) Smart Greenhouse — Monitor Consumes and Commands Actuator
Idea:
- Sensor (Producer) pushes temperatures into a readings queue.
- Monitor consumes readings, decides actions, and posts commands to a command queue.
- Actuator (Consumer) processes commands until shutdown.
File: smart_greenhouse.py
import threading
import queue
import time
from random import randint
from colorama import init, Fore, Style
init(autoreset=True)
READINGS = queue.Queue(maxsize=5)
COMMANDS = queue.Queue(maxsize=1)
NUM_READINGS = 20
SAFE_MIN = 18
SAFE_MAX = 28
CMD_COOL_ON = "COOL_ON"
CMD_HEAT_ON = "HEAT_ON"
CMD_STABLE = "STABLE"
CMD_SHUTDOWN = "SHUTDOWN"
def sensor():
    for _ in range(NUM_READINGS):
        temp = randint(15, 29)
        READINGS.put(temp)
        print(f"{Fore.RED}[Sensor] Produced temp = {temp}C (count={READINGS.qsize()}){Style.RESET_ALL}")
        time.sleep(0.15)
    READINGS.put(None)
def monitor():
    while True:
        temp = READINGS.get()
        if temp is None:
            COMMANDS.put(CMD_SHUTDOWN)
            print(f"{Fore.GREEN}                          [Monitor] SHUTDOWN sent{Style.RESET_ALL}")
            break
        if temp > SAFE_MAX:
            cmd, tag = CMD_COOL_ON, "COOL ON"
        elif temp < SAFE_MIN:
            cmd, tag = CMD_HEAT_ON, "HEAT ON"
        else:
            cmd, tag = CMD_STABLE, "STABLE"
        print(f"{Fore.GREEN}                          [Monitor] temp={temp}C . {tag}{Style.RESET_ALL}")
        COMMANDS.put(cmd)
def actuator():
    while True:
        cmd = COMMANDS.get()
        if cmd == CMD_SHUTDOWN:
            print(f"{Fore.BLUE}             [Actuator] Shutdown received. Exiting.{Style.RESET_ALL}")
            break
        elif cmd == CMD_COOL_ON:
            print(f"{Fore.BLUE}             [Actuator] Cooling ON{Style.RESET_ALL}")
        elif cmd == CMD_HEAT_ON:
            print(f"{Fore.BLUE}             [Actuator] Heating ON{Style.RESET_ALL}")
        elif cmd == CMD_STABLE:
            print(f"{Fore.BLUE}             [Actuator] Systems stable{Style.RESET_ALL}")
        time.sleep(0.2)
def main():
    t_s = threading.Thread(target=sensor)
    t_m = threading.Thread(target=monitor)
    t_a = threading.Thread(target=actuator)
    t_s.start(); t_m.start(); t_a.start()
    t_s.join(); t_m.join(); t_a.join()
    print("\nMain: All threads finished. Exiting...")
if __name__ == "__main__":
    main()
Run:
python smart_greenhouse.py
D) Condition Variable Mini Example
Idea: Demonstrates threading.Condition with the while/wait() pattern, equivalent to the C example.
File: cond_mini_example.py
import threading
import time
from colorama import init, Fore, Style
init(autoreset=True)
data_ready = False
cond = threading.Condition()
def producer():
    time.sleep(1)
    with cond:
        global data_ready
        data_ready = True
        print(f"{Fore.RED}Producer: data is ready!{Style.RESET_ALL}")
        cond.notify()
def consumer():
    with cond:
        while not data_ready:
            print(f"{Fore.BLUE}Consumer: waiting...{Style.RESET_ALL}")
            cond.wait()
        print(f"{Fore.BLUE}Consumer: got the data!{Style.RESET_ALL}")
def main():
    t_c = threading.Thread(target=consumer)
    t_p = threading.Thread(target=producer)
    t_c.start(); t_p.start()
    t_c.join(); t_p.join()
if __name__ == "__main__":
    main()
Run:
python cond_mini_example.py
🧩 Parallels Between C and Python Implementations
| Concept | C API | Python Equivalent | 
|---|---|---|
| Barrier | pthread_barrier_t | threading.Barrier | 
| Mutex | pthread_mutex_t | threading.Lock()orCondition | 
| Condition Variable | pthread_cond_t | threading.Condition() | 
| Producer–Consumer Queue | Manual + pthread_cond_wait | queue.Queue(maxsize=N) | 
| Thread | pthread_create() | threading.Thread() | 
| Sleep / Wait | sleep() / usleep() | time.sleep() | 
| Color Output | ANSI codes | colorama(cross-platform) | 
✅ Key Takeaways for Python
- Threading concepts and patterns mirror those in C closely.
- Python’s queue.Queuesimplifies synchronization for many use cases.
- Condition variables (threading.Condition) follow the samewhile+wait()rule.
- coloramaensures colored output works on Windows, macOS, and Linux.
- Both languages share the same fundamental synchronization ideas — Python just abstracts them more.