Lesson 14advancedThreads

Advanced Threads & Concurrency

Real-world concurrent programming. Features a CLI loading animation, dining philosophers, a thread-safe bank simulation, a shared canvas, spinner animations, and deep dives into condition variable pitfalls and broadcast vs signal semantics.

dining-philosophersproducer-consumermutexsemaphoreshared-memorycond_waitbroadcastfork
Prerequisites:13. Synchronization
1

CLI Loading Animation

A threaded loading spinner/progress bar for command-line interfaces.

01-cli.c99 lines
2

Advanced CLI Simulator

A 285-line advanced terminal UI with progress bars, spinners, and status indicators — all thread-driven.

02-bit-more-cli/cli.c293 lines
3

Fork + Semaphore File Sync

Using named semaphores to synchronize file access between forked processes.

03-forks-mutex.c143 lines
Expected Outputclick Run to execute live
========================================
  Fork + semaphore — file-based sync
========================================

Process 1 (PID 378858) wrote to the file.

========================================
  Fork + semaphore — file-based sync
========================================

Process 2 (PID 378859) wrote to the file.

========================================
  Fork + semaphore — file-based sync
========================================

Process 3 (PID 378860) wrote to the file.

========================================
  Fork + semaphore — file-based sync
========================================

Process 4 (PID 378861) wrote to the file.

========================================
  Fork + semaphore — file-based sync
========================================

Process 5 (PID 378862) wrote to the file.

========================================
  Fork + semaphore — file-based sync
========================================

All child processes have finished writing.
4

IPC Producer-Consumer

A 308-line producer-consumer using shared memory and semaphores across forked processes.

04-forks-mutex.c322 lines
Expected Outputclick Run to execute live
========================================
  IPC producer-consumer with shared me
========================================

Producer 1 produced item 100 at position 0.
Producer 1 produced item 101 at position 2.
Producer 1 produced item 102 at position 4.
Producer 1 produced item 103 at position 1.
Producer 1 produced item 104 at position 3.
Producer 1 produced item 105 at position 0.
Producer 1 produced item 106 at position 2.
Producer 1 produced item 107 at position 4.
Producer 1 produced item 108 at position 1.
Producer 1 produced item 109 at position 3.
Producer 1 finished producing.

========================================
  IPC producer-consumer with shared me
========================================

Producer 2 produced item 200 at position 1.
Producer 2 produced item 201 at position 3.
Producer 2 produced item 202 at position 0.
Producer 2 produced item 203 at position 2.
Producer 2 produced item 204 at position 4.
Producer 2 produced item 205 at position 1.
Producer 2 produced item 206 at position 3.
Producer 2 produced item 207 at position 0.
Producer 2 produced item 208 at position 2.
Producer 2 produced item 209 at position 4.
Producer 2 finished producing.

========================================
  IPC producer-consumer with shared me
========================================

Consumer 2 consumed item 200 from position 1.
Consumer 2 consumed item 201 from position 3.
Consumer 2 consumed item 202 from position 0.
Consumer 2 consumed item 103 from position 1.
Consumer 2 consumed item 104 from position 3.
Consumer 2 consumed item 105 from position 0.
Consumer 2 consumed item 106 from position 2.
Consumer 2 consumed item 107 from position 4.
Consumer 2 consumed item 108 from position 1.
Consumer 2 consumed item 109 from position 3.
Consumer 2 finished consuming.

========================================
  IPC producer-consumer with shared me
========================================

Consumer 1 consumed item 100 from position 0.
Consumer 1 consumed item 101 from position 2.
Consumer 1 consumed item 102 from position 4.
Consumer 1 consumed item 203 from position 2.
Consumer 1 consumed item 204 from position 4.
Consumer 1 consumed item 205 from position 1.
Consumer 1 consumed item 206 from position 3.
Consumer 1 consumed item 207 from position 0.
Consumer 1 consumed item 208 from position 2.
Consumer 1 consumed item 209 from position 4.
Consumer 1 finished consuming.

========================================
  IPC producer-consumer with shared me
========================================

Producer-Consumer simulation completed.
5

Dining Philosophers

The classic dining philosophers problem — five philosophers, five forks, mutex-based deadlock prevention.

05-forks-philosph.c209 lines
Expected Outputclick Run to execute live
[Program timed out after 5 seconds]
6

Thread-Safe Counter

A mutex-protected counter incremented by multiple threads — the canonical thread safety example.

06-pthreads.c78 lines
/*
 * 06-pthreads.c — Thread-safe counter with mutex
 *
 * Key concepts: pthread_mutex_lock/unlock, shared counter, thread safety
 * Compile: gcc -o counter 06-pthreads.c -lpthread
 * Run:     ./prog
 */
// File: thread_safe_counter.c
// Compile with: gcc -Wall -pthread -o thread_safe_counter thread_safe_counter.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define NUM_THREADS 5
#define INCREMENTS_PER_THREAD 1000000

// Shared counter
long long counter = 0;

// Mutex to protect the counter
pthread_mutex_t counter_mutex;

void* increment_counter(void* arg) {
    for (int i = 0; i < INCREMENTS_PER_THREAD; i++) {
        // Lock the mutex before modifying the counter
        pthread_mutex_lock(&counter_mutex);
        
        // Critical section
        counter++;
        
        // Unlock the mutex after modification
        pthread_mutex_unlock(&counter_mutex);
    }
    pthread_exit(NULL);
}

int main() {
    printf("\n");
    printf("========================================\n");
    printf("  Thread-safe counter with mutex\n");
    printf("========================================\n\n");
    pthread_t threads[NUM_THREADS];
    int rc;

    // Initialize the mutex
    if (pthread_mutex_init(&counter_mutex, NULL) != 0) {
        perror("Mutex initialization failed");
        exit(EXIT_FAILURE);
    }

    printf("[Main] %d threads, each incrementing %d times = %d total.\n", NUM_THREADS, INCREMENTS_PER_THREAD, NUM_THREADS * INCREMENTS_PER_THREAD);
    printf("[Main] Using mutex for every increment (safe but slow).\n\n");

    for (long t = 0; t < NUM_THREADS; t++) {
        rc = pthread_create(&threads[t], NULL, increment_counter, NULL);
        if (rc) {
            fprintf(stderr, "[Main] ERROR: pthread_create failed (thread %ld, rc=%d)\n", t, rc);
            exit(EXIT_FAILURE);
        }
        printf("[Main] Thread %ld created\n", t);
    }

    printf("[Main] Waiting for all threads...\n");
    for (int t = 0; t < NUM_THREADS; t++) {
        pthread_join(threads[t], NULL);
    }

    pthread_mutex_destroy(&counter_mutex);

    long long expected = (long long)NUM_THREADS * INCREMENTS_PER_THREAD;
    printf("\n══════════════════════════════════════\n");
    printf("  Expected: %lld\n", expected);
    printf("  Actual:   %lld %s\n", counter, counter == expected ? "✓ correct!" : "← BUG!");
    printf("══════════════════════════════════════\n");
    return 0;
}
Expected Outputclick Run to execute live
========================================
  Thread-safe counter with mutex
========================================

Final counter value: 5000000 (Expected: 5000000)
7

Thread Logging

Multiple threads writing to a shared log with mutex-protected access.

07-pthreads-2.c96 lines
Expected Outputclick Run to execute live
========================================
  Thread logging — ordered output wi
========================================

All threads have finished logging.
8

Bank Simulation

A multi-threaded bank simulation — concurrent deposits and withdrawals with mutex-protected account balances.

08-cool-bank.c102 lines
Expected Outputclick Run to execute live
========================================
  Bank simulation — concurrent accou
========================================

Thread 130924985448128 finished deposit
Thread 130924977055424 finished withdraw
Thread 130924888975040 finished withdraw
Thread 130924897367744 finished deposit
Thread 130924880582336 finished deposit
Thread 130924872189632 finished withdraw
Final account balance: 0
Expected balance: 0
9

Shared Canvas

Multiple threads drawing on a shared 2D canvas, with mutex protection to prevent visual corruption.

09-cool-canvas.c220 lines
10

Spinner Animations

Multiple concurrent terminal spinner animations — each thread runs its own spinner pattern.

10-spinners.c178 lines
11

Condition Variable Pitfalls

What goes wrong when you use condition variables incorrectly — missed signals and spurious wakeups.

11-5-cond-waiting-bad.c69 lines
/*
 * 11-5-cond-waiting-bad.c — Condition variable pitfalls — what can go wrong
 *
 * Key concepts: Missed signals, spurious wakeups, why while-loop is needed
 * Compile: gcc -o cond_bad 11-5-cond-waiting-bad.c -lpthread
 * Run:     ./cond_bad
 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

#define NUM_THREADS 2

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int condition_met = 0;

void* thread_func(void* arg) {
    long id = (long)arg;
    printf("[Thread %ld] Starting — will wait for condition...\n", id);
    pthread_mutex_lock(&mutex);
    while (!condition_met) {
        printf("[Thread %ld] Calling cond_wait (blocking)...\n", id);
        pthread_cond_wait(&cond, &mutex);
        printf("[Thread %ld] Woke up! Checking condition...\n", id);
    }
    pthread_mutex_unlock(&mutex);

    printf("[Thread %ld] Condition met — proceeding.\n", id);
    return NULL;
}

int main() {
    printf("\n");
    printf("══════════════════════════════════════\n");
    printf("  Condition Variable Pitfalls\n");
    printf("══════════════════════════════════════\n\n");
    printf("[Main] BUG DEMO: signals sent BEFORE threads are created!\n");
    printf("[Main] The signals are lost — threads will block forever.\n\n");

    pthread_t threads[NUM_THREADS];

    // BUG: Sending signals before any thread is waiting
    printf("[Main] Sending cond_signal #1... (nobody is listening!)\n");
    pthread_cond_signal(&cond);
    printf("[Main] Sending cond_signal #2... (still nobody listening!)\n");
    pthread_cond_signal(&cond);
    printf("[Main] Both signals are LOST — they don't queue up.\n\n");

    // Create threads after signals — they'll never wake up
    for (long i = 0; i < NUM_THREADS; i++) {
        pthread_create(&threads[i], NULL, thread_func, (void*)i);
    }

    printf("[Main] Threads created. They're waiting for signals that already fired.\n");
    printf("[Main] This program will hang forever! (Ctrl+C to exit)\n");
    printf("[Main] Fix: set condition_met=1 before signaling, or signal after threads start.\n");

    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    return 0;
}
Expected Outputclick Run to execute live
[Program timed out after 5 seconds]
12

Simple Condition Variable

The correct way to use pthread_cond_wait with a predicate in a while loop.

12-cond-simple.c75 lines
/*
 * 12-cond-simple.c — Simple correct condition variable usage
 *
 * Key concepts: pthread_cond_wait in while loop, predicate check pattern
 * Compile: gcc -o cond_ok 12-cond-simple.c -lpthread
 * Run:     ./cond_ok
 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

// Spurious wakeups: https://en.wikipedia.org/wiki/Spurious_wakeup
// This is why we use while() not if() around cond_wait!

#define NUM_THREADS 20

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int condition_met = 0;

void* thread_func(void* arg) {
    long id = (long)arg;

    pthread_mutex_lock(&mutex);
    while (!condition_met) {
        // while-loop protects against spurious wakeups:
        // if a thread wakes without condition_met, it goes back to sleep
        pthread_cond_wait(&cond, &mutex);
    }
    pthread_mutex_unlock(&mutex);

    printf("[Thread %2ld] Condition met — proceeding!\n", id);
    return NULL;
}

int main() {
    printf("\n");
    printf("══════════════════════════════════════\n");
    printf("  Correct Condition Variable Usage\n");
    printf("══════════════════════════════════════\n\n");
    printf("[Main] Creating %d threads. All will wait on cond_wait.\n", NUM_THREADS);
    printf("[Main] After 2 seconds, main sets condition=1 and signals each one.\n\n");

    pthread_t threads[NUM_THREADS];

    for (long i = 0; i < NUM_THREADS; i++) {
        pthread_create(&threads[i], NULL, thread_func, (void*)i);
    }
    printf("[Main] All %d threads created and waiting.\n", NUM_THREADS);

    sleep(2);

    printf("\n[Main] Setting condition_met = 1\n");
    pthread_mutex_lock(&mutex);
    condition_met = 1;
    pthread_mutex_unlock(&mutex);

    printf("[Main] Sending cond_signal to each thread (one at a time)...\n\n");
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_cond_signal(&cond);
        usleep(100000); // Small delay to observe sequential waking
    }

    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    printf("\n[Main] All %d threads finished. Compare with 11-5 (the broken version).\n", NUM_THREADS);
    return 0;
}
Expected Outputclick Run to execute live
========================================
  Simple correct condition variable us
========================================

Thread 0 proceeding.
Thread 1 proceeding.
Thread 2 proceeding.
Thread 3 proceeding.
Thread 4 proceeding.
Thread 5 proceeding.
Thread 6 proceeding.
Thread 7 proceeding.
Thread 8 proceeding.
Thread 9 proceeding.
Thread 10 proceeding.
Thread 11 proceeding.
Thread 12 proceeding.
Thread 13 proceeding.
Thread 14 proceeding.
Thread 15 proceeding.
Thread 16 proceeding.
Thread 17 proceeding.
Thread 18 proceeding.
Thread 19 proceeding.
13

Broadcast vs Signal

Comparing pthread_cond_signal (wake one) and pthread_cond_broadcast (wake all) — when to use each.

13-broadcast.c71 lines
/*
 * 13-broadcast.c — Broadcast vs Signal — pthread_cond_broadcast
 *
 * Key concepts: pthread_cond_signal (wake one) vs pthread_cond_broadcast (wake all)
 * Compile: gcc -o broadcast 13-broadcast.c -lpthread
 * Run:     ./broadcast
 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

#define NUM_THREADS 20

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int condition_met = 0;

void* thread_func(void* arg) {
    long id = (long)arg;

    pthread_mutex_lock(&mutex);
    while (!condition_met) {
        pthread_cond_wait(&cond, &mutex);
        // When broadcast fires, ALL threads wake up simultaneously.
        // But only one can hold the mutex at a time — they take turns.
        printf("[Thread %2ld] Woke up! Re-checking condition (mutex acquired)...\n", id);
    }
    pthread_mutex_unlock(&mutex);

    printf("[Thread %2ld] Proceeding — condition is true.\n", id);
    return NULL;
}

int main() {
    printf("\n");
    printf("══════════════════════════════════════\n");
    printf("  Broadcast vs Signal\n");
    printf("══════════════════════════════════════\n\n");
    printf("[Main] cond_signal:    wakes ONE waiting thread\n");
    printf("[Main] cond_broadcast: wakes ALL waiting threads\n\n");
    printf("[Main] Creating %d threads, all will call cond_wait...\n", NUM_THREADS);

    pthread_t threads[NUM_THREADS];

    for (long i = 0; i < NUM_THREADS; i++) {
        pthread_create(&threads[i], NULL, thread_func, (void*)i);
    }

    sleep(2);

    printf("\n[Main] All threads are waiting. Calling pthread_cond_broadcast!\n");
    printf("[Main] All %d threads will wake up at once (but mutex serializes them).\n\n", NUM_THREADS);

    pthread_mutex_lock(&mutex);
    condition_met = 1;
    pthread_cond_broadcast(&cond);
    pthread_mutex_unlock(&mutex);

    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    printf("\n[Main] All %d threads finished.\n", NUM_THREADS);
    printf("[Main] Compare with 12-cond-simple.c where we used signal (one at a time).\n");
    return 0;
}
Expected Outputclick Run to execute live
========================================
  Broadcast vs Signal — pthread_cond
========================================

Thread 19 woke up.
Thread 0 woke up.
Thread 19 proceeding.
Thread 2 woke up.
Thread 0 proceeding.
Thread 1 woke up.
Thread 2 proceeding.
Thread 1 proceeding.
Thread 3 woke up.
Thread 4 woke up.
Thread 3 proceeding.
Thread 4 proceeding.
Thread 5 woke up.
Thread 6 woke up.
Thread 5 proceeding.
Thread 6 proceeding.
Thread 7 woke up.
Thread 7 proceeding.
Thread 9 woke up.
Thread 9 proceeding.
Thread 10 woke up.
Thread 10 proceeding.
Thread 11 woke up.
Thread 11 proceeding.
Thread 12 woke up.
Thread 12 proceeding.
Thread 13 woke up.
Thread 13 proceeding.
Thread 14 woke up.
Thread 14 proceeding.
Thread 15 woke up.
Thread 15 proceeding.
Thread 16 woke up.
Thread 16 proceeding.
Thread 17 woke up.
Thread 17 proceeding.
Thread 18 woke up.
Thread 18 proceeding.
Thread 8 woke up.
Thread 8 proceeding.

Open Full Terminal

Get a Linux terminal with all 13 files from this lesson pre-loaded. Compile, run, and experiment freely.