Lesson 12advancedThreads

POSIX Threads

Introduction to multi-threaded programming with pthreads. Covers thread creation, exit and return values, cleanup handlers, join, pthread_once, parallel partial sum, race conditions, and combining threads with other IPC mechanisms like FIFOs.

pthreadpthread_createpthread_jointhreadsrace-condition
1

Creating Threads

The basics of pthread_create — spawning a thread with a function pointer and argument.

01-pthread_create.c68 lines
/*
 * 01-pthread_create.c — Creating your first thread
 *
 * Demonstrates: pthread_create() and pthread_exit()
 * Key concepts: Thread vs process, pthread_self(), shared address space
 * Compile: gcc -o thread1 01-pthread_create.c -lpthread
 * Run:     ./thread1
 *
 * Note: Both the main thread and the new thread run my_func concurrently.
 *       The interleaved output shows real parallelism.
 */

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>   // for pthread_create(), pthread_exit(), pthread_self()
#include <unistd.h>    // for getpid(), sleep()
#include <time.h>      // for time() (seeding rand)

// Thread function prototype
void* my_func(void *arg);

int main() {
    printf("\n");
    printf("========================================\n");
    printf("  pthread_create — Your First Thread\n");
    printf("========================================\n\n");
    pthread_t thread_data;
    int a = 1;  // Value passed to thread
    int status;

    // Seed random number generator
    srand((unsigned) time(NULL));

    // Create a new thread running my_func, passing a pointer to 'a'
   /*
   pthread_create(&thread_data, NULL, my_func, (void *)&a)
&thread_data: The thread ID is stored here.
NULL: Using default thread attributes.
my_func: Function executed by the thread.
(void *)&a: The argument passed to the thread (a pointer to a).*/
    printf("[Main] Creating a new thread...\n");
    status = pthread_create(&thread_data, NULL, my_func, (void *) &a);
    if (status != 0) {
        fputs("[Main] ERROR: pthread_create failed!\n", stderr);
        exit(EXIT_FAILURE);
    }
    printf("[Main] Thread created (ID: %lu). Now main also calls my_func.\n", (unsigned long)thread_data);
    printf("[Main] Watch the interleaved output — both threads run concurrently!\n\n");

    // Main thread also runs my_func (simulate concurrency)
    my_func((void *)&a);
    puts("This line is never reached because both threads call pthread_exit");
    return EXIT_SUCCESS;
}

void* my_func(void *arg) {
    int i;
    int *val = (int *) arg;
    // Loop 5 times, printing process and thread info
    for (i = 0; i < 5; i++) {
        printf("[Thread %lu] PID=%d, iteration %d/5 (val=%d)\n",
               (unsigned long) pthread_self(), getpid(), i + 1, *val);
        sleep(rand() % 3); // Sleep for a random time between 0-2 seconds
    }
    // Exit the thread
    pthread_exit(NULL);
}
Expected Outputclick Run to execute live
[Program timed out after 5 seconds]
2

Thread Exit with Return Values

Using pthread_exit to return a value from a thread, collected by pthread_join.

02-pthread_exit.c81 lines
Expected Outputclick Run to execute live
========================================
  pthread_exit — Return Values
========================================

17 38 79 3879 
 -17 -38 -79 -3879
3

Cleanup Handlers

Registering cleanup handlers with pthread_cleanup_push/pop — they run when a thread is cancelled or exits.

03-pthread_cleanup.c86 lines
4

Joining Threads

Waiting for threads to complete with pthread_join and retrieving their return values.

04-pthread_join.c85 lines
Expected Outputclick Run to execute live
[Program timed out after 5 seconds]
5

Ping-Pong (Busy Waiting)

Two threads taking turns printing "ping" and "pong" using a shared flag — a deliberate busy-wait anti-pattern.

05-pthread_ping_pong.c75 lines
/*
 * 05-pthread_ping_pong.c — Ping-pong between threads using busy waiting
 *
 * Key concepts: Shared flag, busy waiting (spin loop), thread coordination
 * Compile: gcc -o pingpong 05-pthread_ping_pong.c -lpthread
 * Run:     ./prog
 */
// File: pthread_pingpong.c
// Compile with: gcc -Wall -pthread pthread_pingpong.c -o pthread_pingpong

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

// Global variable to indicate whose turn it is (0 or 1)
volatile int turn = 0;

// Thread function prototype
void* pingpong(void *arg);

int main() {
    printf("\n");
    printf("========================================\n");
    printf("  Ping-Pong — Busy-Wait Sync\n");
    printf("========================================\n\n");
    pthread_t threads[2];
    int ids[2] = {0, 1};
    int status;

    // Seed random number generator.
    srand((unsigned) time(NULL));

    printf("[Main] Two threads take turns printing — coordinated via a shared 'turn' flag.\n");
    printf("[Main] Thread 0 = Ping, Thread 1 = Pong. Each waits in a spin loop (busy wait).\n");
    printf("[Main] Note: busy waiting wastes CPU! Better approaches: mutex + cond_wait (lesson 13).\n\n");

    for (int i = 0; i < 2; i++) {
        status = pthread_create(&threads[i], NULL, pingpong, &ids[i]);
        if (status != 0) {
            fprintf(stderr, "[Main] ERROR: pthread_create failed (rc=%d)\n", status);
            exit(EXIT_FAILURE);
        }
    }

    for (int i = 0; i < 2; i++) {
        pthread_join(threads[i], NULL);
    }
    printf("\n[Main] Ping-Pong complete — 5 rounds each, perfectly alternating.\n");
    return EXIT_SUCCESS;
}

void* pingpong(void *arg) {
    int id = *((int *) arg);
    // Each thread will print its message 5 times.
    for (int round = 0; round < 5; round++) {
        // Busy wait until it is this thread's turn.
        while (turn != id) {
            ; // Do nothing
        }

        // Print message: "Ping" or "Pong"
        if (id == 0)
            printf("[Thread 0] Ping! (round %d)\n", round + 1);
        else
            printf("[Thread 1]   Pong! (round %d)\n", round + 1);

        // Let the other thread proceed.
        turn = 1 - id;
        sleep(1); // Simulate work
    }
    pthread_exit(NULL);
}
Expected Outputclick Run to execute live
[Program timed out after 5 seconds]
6

pthread_once

Ensuring an initialization function runs exactly once across all threads using pthread_once.

06-pthread-once.c73 lines
/*
 * 06-pthread-once.c — pthread_once — initialization that runs exactly once
 *
 * Key concepts: pthread_once_t, thread-safe one-time init
 * Compile: gcc -o once 06-pthread-once.c -lpthread
 * Run:     ./prog
 */
// File: pthread_once_example.c
// Compile with: gcc -Wall -pthread pthread_once_example.c -o pthread_once_example

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

void* my_func(void *arg);
void init(); // This function will be called only once

int *arr;
int counter = 0;
pthread_once_t once_control = PTHREAD_ONCE_INIT;

int main() {
    printf("\n");
    printf("========================================\n");
    printf("  pthread_once — One-Time Init\n");
    printf("========================================\n\n");
    pthread_t threads[5];
    int i, status;

    printf("[Main] Creating 5 threads. Each calls pthread_once() to ensure init() runs ONCE.\n");
    printf("[Main] Only the FIRST thread to call pthread_once will execute init().\n\n");

    for (i = 0; i < 5; i++) {
        status = pthread_create(&threads[i], NULL, my_func, NULL);
        if (status != 0) {
            fprintf(stderr, "[Main] ERROR: pthread_create failed\n");
            exit(EXIT_FAILURE);
        }
        printf("[Main] Created thread %d\n", i);
    }

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

    printf("\n[Main] All threads finished. Shared array contents: ");
    for (i = 0; i < counter; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n[Main] init() ran exactly once, even though 5 threads called pthread_once.\n");
    free(arr);
    return EXIT_SUCCESS;
}

void* my_func(void *arg) {
    // Ensure that init() is called exactly once.
    pthread_once(&once_control, init);
    // For demonstration, each thread adds a random number to the shared array.
    arr[counter++] = rand() % 10;
    pthread_exit(NULL);
}

void init() {
    printf("  [init()] This runs EXACTLY ONCE — allocating shared array.\n\n");
    srand(time(NULL));
    arr = (int *) malloc(5 * sizeof(int));
    if (!arr) {
        perror("malloc() failed");
        exit(EXIT_FAILURE);
    }
}
Expected Outputclick Run to execute live
========================================
  pthread_once — One-Time Init
========================================

In init()
6 7 8 3 3 
Done
7

Parallel Partial Sum

Splitting an array across threads, each computing a partial sum, then combining results — a real parallelism speedup.

07-partial-sum.c87 lines
Expected Outputclick Run to execute live
========================================
  Parallel Partial Sum
========================================

Initializing array with values: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20

Creating 4 threads to compute partial sums...

Thread 0: processing elements from index 0 to 4 (values: 1, 2, 3, 4, 5)
Thread 0: computed partial sum = 15
Thread 1: processing elements from index 5 to 9 (values: 6, 7, 8, 9, 10)
Thread 1: computed partial sum = 40
Thread 2: processing elements from index 10 to 14 (values: 11, 12, 13, 14, 15)
Thread 2: computed partial sum = 65
Main thread: collected partial sum from thread 0 = 15
Main thread: collected partial sum from thread 1 = 40
Main thread: collected partial sum from thread 2 = 65
Thread 3: processing elements from index 15 to 19 (values: 16, 17, 18, 19, 20)
Thread 3: computed partial sum = 90
Main thread: collected partial sum from thread 3 = 90

Total sum of the array = 210
8

Main Exits Before Threads

What happens when main() returns while threads are still running — demonstrating the need for join or pthread_exit in main.

08-pthread-exit.c57 lines
/*
 * 08-pthread-exit.c — Main thread exits early, worker threads continue
 *
 * Key concepts: pthread_exit from main, process stays alive for threads
 * Compile: gcc -o early_exit 08-pthread-exit.c -lpthread
 * Run:     ./prog
 */
// File: main_exits_but_threads_continue.c
// Compile with: gcc -Wall -pthread main_exits_but_threads_continue.c -o main_exits_but_threads_continue

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

// Thread function: prints its ID and iteration count.
void* thread_func(void *arg) {
    int id = *((int *) arg);
    for (int i = 0; i < 10; i++) {
        printf("[Thread %d] Iteration %d/10 — still running after main exited!\n", id, i + 1);
        sleep(1);
    }
    printf("[Thread %d] Finished all 10 iterations.\n", id);
    pthread_exit(NULL);
}

int main() {
    printf("\n");
    printf("========================================\n");
    printf("  Main Exits, Threads Continue\n");
    printf("========================================\n\n");
    pthread_t threads[3];
    int thread_ids[3] = {1, 2, 3};
    
    printf("[Main] Key concept: if main calls return/exit, ALL threads die.\n");
    printf("[Main] But if main calls pthread_exit, the process stays alive for worker threads!\n\n");

    for (int i = 0; i < 3; i++) {
        if (pthread_create(&threads[i], NULL, thread_func, (void *) &thread_ids[i]) != 0) {
            perror("[Main] ERROR: pthread_create failed");
            exit(EXIT_FAILURE);
        }
        printf("[Main] Created thread %d\n", thread_ids[i]);
    }

    printf("\n[Main] Calling pthread_exit() NOW — main thread stops, but threads keep running!\n");
    printf("[Main] Watch: the thread output continues even though main is gone.\n\n");
    
    // Instead of exiting normally (which would end the process and kill all threads),
    // the main thread calls pthread_exit. This causes the main thread to finish its work
    // while keeping the process alive until all non-detached threads complete.
    pthread_exit(NULL);
    
    // Note: Any code after pthread_exit() will not be executed.
    return EXIT_SUCCESS;
}
Expected Outputclick Run to execute live
[Program timed out after 5 seconds]
9

Countdown Race Condition

Multiple threads decrementing a shared counter without synchronization — observe the race condition in action.

09-countdown-race.c98 lines
Expected Outputclick Run to execute live
[Program timed out after 5 seconds]
10

Async Chat Simulator

A threaded chat simulator where one thread reads input and another displays messages asynchronously.

10-async-chat.c81 lines
Expected Outputclick Run to execute live
[Program timed out after 5 seconds]
11

Threads + FIFO

Combining pthreads with named pipes (FIFO) — one thread reads from a FIFO while the main thread handles other work.

11-pthreads-fifo.c132 lines
Expected Outputclick Run to execute live
========================================
  Threads + FIFO Communication
========================================

Writer: Sent "Message 0 from writer"
Reader: Received "Message 0 from writer"
Reader: Sent "Acknowledged: Message 0 from writer"
Writer: Received "Acknowledged: Message 0 from writer"

========================================
  Threads + FIFO Communication
========================================

Writer: Sent "Message 1 from writer"
Reader: Received "Message 1 from writer"
Reader: Sent "Acknowledged: Message 1 from writer"
Writer: Received "Acknowledged: Message 1 from writer"

========================================
  Threads + FIFO Communication
========================================

Writer: Sent "Message 2 from writer"
Reader: Received "Message 2 from writer"
Reader: Sent "Acknowledged: Message 2 from writer"
Writer: Received "Acknowledged: Message 2 from writer"

========================================
  Threads + FIFO Communication
========================================

Writer: Sent "Message 3 from writer"
Reader: Received "Message 3 from writer"
Reader: Sent "Acknowledged: Message 3 from writer"
Writer: Received "Acknowledged: Message 3 from writer"

========================================
  Threads + FIFO Communication
========================================

Writer: Sent "Message 4 from writer"
Reader: Received "Message 4 from writer"
Reader: Sent "Acknowledged: Message 4 from writer"
Writer: Received "Acknowledged: Message 4 from writer"

========================================
  Threads + FIFO Communication
========================================

Writer: Sent "Message 5 from writer"
Reader: Received "Message 5 from writer"
Reader: Sent "Acknowledged: Message 5 from writer"
Writer: Received "Acknowledged: Message 5 from writer"

========================================
  Threads + FIFO Communication
========================================

Writer: Sent "Message 6 from writer"
Reader: Received "Message 6 from writer"
Reader: Sent "Acknowledged: Message 6 from writer"
Writer: Received "Acknowledged: Message 6 from writer"

========================================
  Threads + FIFO Communication
========================================

Writer: Sent "Message 7 from writer"
Reader: Received "Message 7 from writer"
Reader: Sent "Acknowledged: Message 7 from writer"
Writer: Received "Acknowledged: Message 7 from writer"

========================================
  Threads + FIFO Communication
========================================

Writer: Sent "Message 8 from writer"
Reader: Received "Message 8 from writer"
Reader: Sent "Acknowledged: Message 8 from writer"
Writer: Received "Acknowledged: Message 8 from writer"

========================================
  Threads + FIFO Communication
========================================

Writer: Sent "Message 9 from writer"
Reader: Received "Message 9 from writer"
Reader: Sent "Acknowledged: Message 9 from writer"
Writer: Received "Acknowledged: Message 9 from writer"

Open Full Terminal

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