Thread Synchronization

Tutorial 2 — Techniques for Thread Synchronization

Duration: ~1 hour
Audience: Undergraduate students familiar with basic threading Languages: C (POSIX Threads), optional Python demos
Environment: Linux / Windows (WSL recommended)


Learning Objectives

By the end of this tutorial, students will be able to:

  1. Use mutexes, condition variables, and barriers to coordinate threads.
  2. Identify and prevent race conditions and deadlocks.
  3. Understand how condition variables work internally.
  4. 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(&not_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(&not_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(&not_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(&not_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(&not_full, &mtx);

        buffer[in_idx] = temp;
        in_idx = (in_idx + 1) % BUFFER_SIZE;
        count++;
        pthread_cond_signal(&not_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(&not_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(&not_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(&not_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() or Condition
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.Queue simplifies synchronization for many use cases.
  • Condition variables (threading.Condition) follow the same while + wait() rule.
  • colorama ensures colored output works on Windows, macOS, and Linux.
  • Both languages share the same fundamental synchronization ideas — Python just abstracts them more.
Previous
Next