130 / 163 · C11 · 8 min
Concurrency: An Introduction
This chapter presents threads from a fresh angle: a single process may contain several independent flows of execution that all share one address space. Every thread carries its own program counter and registers, so a switch among them leaves the page table untouched. Each thread also owns a private stack. Threads exist mainly so that multiple cores can work in true parallel and so that a program can keep making progress while some of its threads wait for I/O.
In this lesson
What a Thread Really Is
Think of a thread as a lightweight flow of execution living inside a process. It owns a private program counter and a full register set, so the scheduler can bounce among threads much as it bounces among processes. The crucial difference is that every thread of the same process sees exactly the same virtual memory; global variables and heap objects are therefore immediately visible to all of them.
Why Bother with Threads
Contemporary machines contain several processor cores. Chop a large computation into pieces and hand each piece to a thread; those pieces can then run truly simultaneously. A second important reason is that when one thread stalls on disk or network I/O, the remaining threads of the same process can still occupy the CPU and keep the whole program moving.
Multiple Stacks inside One Address Space
A classic single-threaded process owns just one stack, normally sitting near the bottom of the address space. Once several threads exist, each must have its own stack that stores locals, arguments and return addresses. Those stacks occupy distinct corners of the shared virtual address space, while the heap remains unique and is usable by every thread.
Pitfalls
- Assuming threads always run in the exact order they were created
- Reading and writing shared variables before the later chapters introduce synchronization
Run an example
Minimum C11 · complete program · Download .c
#include <stdio.h>
#include <pthread.h>
int results[2] = {0, 0};
void *thread_one(void *unused) { (void)unused;
results[0] = 10 + 20;
return NULL;
}
void *thread_two(void *unused) { (void)unused;
results[1] = 30 + 40;
return NULL;
}
int main(void) {
pthread_t t1, t2;
printf("main: begin\n");
pthread_create(&t1, NULL, thread_one, NULL);
pthread_create(&t2, NULL, thread_two, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("result0=%d result1=%d\n", results[0], results[1]);
printf("main: end\n");
return 0;
}
Compile locally
gcc -std=c11 -Wall -Wextra -Wpedantic -Werror -pthread ostep-26-threads.c -o example && ./exampleExpected result
main: begin
result0=30 result1=70
main: end
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
Why does a context switch among threads skip the page-table change that a process switch performs?
Show a reference answer
Because all threads of one process already share the identical address space, the hardware page-table register can stay unchanged.
Check the sources
Drafts and official chapters change. The version mark is only the example’s minimum.