Threads vs. processes
Understand the difference between processes and threads in C — memory isolation, the cost of context switching, and when to use each.
- Explain what a process is and what resources it owns
- Explain what a thread is and how threads share a process's address space
- Describe the trade-offs between thread-level and process-level concurrency
- Identify the main risks of shared memory between threads
Modern computers have multiple cores. To use them, programs need to do more than one thing at a time. In C, you choose between processes (isolated programs) and threads (concurrent execution paths within a single program). Understanding the difference determines how you design concurrent systems.
What is a process?
A process is an instance of a running program with its own:
- Virtual address space (the process cannot read another process's memory).
- Open file descriptors.
- Signal handlers.
- User and group IDs.
- Environment variables.
Creating a new process with fork() copies the parent's address space (using copy-on-write). Processes communicate through pipes, sockets, shared memory (explicitly requested), or files. The OS kernel isolates them — a crash in one process does not corrupt another.
What is a thread?
A thread is a concurrent execution path within a single process. All threads in a process share:
- The same virtual address space (all globals, heap, and open files).
- The same file descriptors.
- Signal handlers (though signal masks can differ).
Each thread has its own:
- Stack.
- CPU registers and program counter.
- Thread-local storage (declared with
_Thread_localor__thread).
Creating a thread vs. a process
| Aspect | Process (fork) | Thread (pthread_create) |
|---|---|---|
| Memory | Isolated copy | Shared address space |
| Creation cost | High (copy page tables) | Low |
| Communication | IPC: pipes, sockets, shm | Shared variables (with synchronisation) |
| Crash isolation | Yes | No — one thread can corrupt all |
| Parallelism | Yes (multi-process) | Yes (multi-thread, same process) |
For CPU-bound work across multiple cores, threads are the natural fit. For isolation, security boundaries, or running separate programs, processes are appropriate.
The data race problem
When multiple threads access shared memory and at least one is writing, without synchronisation, you have a data race — the result is undefined behaviour:
int counter = 0; /* shared between two threads */
void *increment(void *arg) {
for (int i = 0; i < 1000000; i++) {
counter++; /* NOT ATOMIC: read-modify-write is three operations */
}
return NULL;
}counter++ compiles to (approximately): load counter into a register, add 1, store back. If two threads execute this simultaneously, one thread's update can be lost:
Thread 1: load counter (value: 5)
Thread 2: load counter (value: 5)
Thread 1: add 1 → 6, store → counter = 6
Thread 2: add 1 → 6, store → counter = 6 ← Thread 1's update lostAfter a million increments from each of two threads, counter should be 2,000,000 but might be anything less.
POSIX threads (pthreads)
On Linux and macOS, the POSIX thread library (pthreads) provides the standard threading API. The functions are in <pthread.h> and the library is linked with -lpthread:
gcc -Wall -pthread program.c -o program(Use -pthread rather than -lpthread — it also sets the right preprocessor macros.)
The core functions are:
pthread_create— create a thread.pthread_join— wait for a thread to finish.pthread_mutex_lock/pthread_mutex_unlock— synchronise access to shared data.pthread_cond_wait/pthread_cond_signal— coordinate between threads.
These are covered in the following lessons.
C11 added <threads.h>. The C11 standard includes a thread API (thrd_create, mtx_lock, etc.) that abstracts over platform differences. In practice, POSIX threads are more widely supported and have more documentation. The concepts are the same; the function names differ.
Where to go next
Next: creating threads with pthread_create — the full API for starting, identifying, and joining threads.