A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
A thread is a separate path of execution within the same process. Threads share the address space: global variables, heap memory, file descriptors. Each thread has its own stack, its own registers, its own program counter. The POSIX threads library (pthreads) provides the API: pthread_create to launch a thread, pthread_join to wait for it, pthread_exit to terminate it. On Linux, link with -lpthread. On macOS, pthreads is part of libSystem and no extra flag is needed.
#include <stdio.h>#include <pthread.h>#include <unistd.h>void *worker(void *arg) {int id = *(int *)arg;printf("Thread %d: starting\n", id);sleep(1);printf("Thread %d: done\n", id);return NULL;}int main(void) {pthread_t threads[3];int ids[3] = {1, 2, 3};for (int i = 0; i < 3; i++) {int result = pthread_create(&threads[i], NULL, worker, &ids[i]);if (result != 0) {fprintf(stderr, "pthread_create failed: %d\n", result);return 1;}}printf("Main: waiting for threads\n");for (int i = 0; i < 3; i++) {pthread_join(threads[i], NULL);}printf("Main: all threads finished\n");return 0;}
Three threads launched. They run concurrently. The order of "starting" messages is nondeterministic. The order of "done" messages is nondeterministic. The only guarantee is that main does not print "all threads finished" until all three joins complete. pthread_join blocks the calling thread until the target thread exits. Without join, main could exit while threads are still running, killing them.
The thread function must match void *(*start_routine)(void *). It takes a void pointer and returns a void pointer. The argument is whatever you passed as the fourth argument to pthread_create. The return value is retrieved by pthread_join's second argument if you provide one. The void pointer types mean you must cast. There is no type safety. This is the C way: generic interface, manual casting, programmer responsibility.
The argument to pthread_create must remain valid until the thread reads it. Passing the address of a loop variable that changes on the next iteration is a classic bug: all threads see the final value. Pass the address of a stable variable, or allocate the argument on the heap and let the thread free it.
Threads are the gateway to concurrent C programming. They share memory, which makes communication easy and synchronization hard. The next module covers race conditions, mutexes, and detached threads. Advanced topics module complete. Next: the final module, where we tackle concurrency bugs, security patterns, and the capstone challenge.
Finish the lesson once you have worked through the material. This awards ★ 40 XP.