Threads
A thread is an independent flow of execution inside a process.
Concept
Threads vs Processes A thread is an independent flow of execution inside a process. Threads of the same process share the address space (code, globals, heap) and open files, but each has its own registers, program counter and stack.
Shared with other threads of the process Higher (new page tables, copy-on-write setup) IPC (pipes, sockets, shared memory)
Shared variables (needs synchronization) A crash affects only that process A crash kills the whole process Must switch address space (TLB effects) Cheaper within the same process
Kernel (1:1) threads: each user thread is a kernel-scheduled task — Linux (NPTL), Windows, macOS. On Linux a thread is a task created by clone() with shared memory; it has its own TID while all threads share the PID (thread group ID).
User-level (N:1) threads: scheduled by a library inside one kernel thread; cheap but one blocking call blocks all.
M:N / green threads: many user tasks multiplexed on a few kernel threads — Go goroutines, Java virtual threads, Erlang processes, async runtimes.
Why Use Threads Parallelism: use several CPU cores for one computation.
Concurrency: keep a UI or server responsive while waiting for I/O.
Structure: separate activities (acquisition, processing, logging).
The price is shared-state bugs: races, deadlocks, visibility problems (Modules 15, 16, 26).
Why It Matters
Servers, GUIs, games, data acquisition on test benches and every modern runtime (JVM, .NET, Node's thread pool) are multithreaded. Knowing what threads share, what they don't, and what they cost is fundamental.
Syntax
pthread_t t;
pthread_create(&t, NULL, worker, arg); /* compile and link with -pthread */
pthread_join(t, &result);
pthread_detach(t);Example
13.1 Threads share the process, have their own stacks, and are cheaper than processes
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <time.h>
int shared_value = 0; /* one copy, visible to all threads */
typedef struct { int id; long sum; } Work;
static void *worker(void *arg)
{
Work *w = arg;
int local = w->id * 100; /* each thread has its own stack */
for (int i = 1; i <= 1000; i++) w->sum += i * (w->id + 1);
printf("thread %d: kernel tid %ld, pid %d, &local %p\n", w->id, (long)syscall(SYS_gettid), getpid(), (void *)&local);
return NULL;
}
static double now(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec + t.tv_nsec / 1e9; }
static void *nothing(void *a) { return a; }
int main(void)
{
pthread_t t[3];
Work w[3];
for (int i = 0; i < 3; i++) { w[i] = (Work){i, 0}; pthread_create(&t[i], NULL, worker, &w[i]); }
for (int i = 0; i < 3; i++) pthread_join(t[i], NULL); /* all three run concurrently */
for (int i = 0; i < 3; i++) printf("result of thread %d: %ld\n", i, w[i].sum);
printf("main : kernel tid %ld, pid %d (the main thread's tid equal
…Expected Result
All three threads report the same PID but different kernel TIDs, and their local variables live at different addresses about 8 MB apart — separate stacks (the default thread stack size on Linux is 8 MB).
The main thread's TID equals the PID.
Creating and joining a thread was roughly twice as fast as forking and reaping a process here. (Absolute times vary a lot by system; process creation is usually the more expensive of the two.)
thread 0: kernel tid 38, pid 37, &local 0x7e392effee34
thread 1: kernel tid 39, pid 37, &local 0x7e392e7fde34
thread 2: kernel tid 40, pid 37, &local 0x7e392dffce34
result of thread 0: 500500
result of thread 1: 1001000
result of thread 2: 1501500
main : kernel tid 37, pid 37 (the main thread's tid equals the pid)
create+join a thread : 116.1 us
fork+wait a process : 247.8 usCommon Mistakes
Passing a pointer to a loop variable to several threads (they all see the final value).
Returning a pointer to a thread's local variable from the thread function.
Forgetting to join or detach threads (resource leak).
Assuming threads run in creation order or at the same speed.
Using non-thread-safe library functions (strtok, localtime) from several threads.
Best Practices
Prefer thread pools and task queues to creating a thread per job.
Minimize shared mutable state; pass ownership of data between threads via queues.
Name threads (pthread_setname_np) for debugging.
Match the number of CPU-bound threads to the number of cores.
Interview Relevance
Very high: "process vs thread?", "what do threads share?", "user vs kernel threads?", "why is a thread cheaper than a process?", "what happens if one thread crashes?".
Practice Questions
Sum a 100-million-element array with 1, 2, 4 and 8 threads and measure the speedup.
Print /proc/self/task from a program with 4 threads.
Demonstrate the loop-variable bug and fix it.
Change the thread stack size with pthread_attr_setstacksize and verify it.
Quick Quiz
Q1. (Multiple choice) Which is NOT shared between threads of one process? A) Heap B) Global variables C) Open file descriptors D) Stack Answer: D.
Q2. (Predict the output) Do threads of one process have the same PID on Linux?
Answer: Yes; they have different TIDs (verified).
Q3. (True/False) If one thread dereferences a NULL pointer, only that thread dies.
Answer: False. The SIGSEGV terminates the whole process by default.
Q4. (Find the bug) for (int i = 0; i < 4; i++) pthread_create(&t[i], NULL, work, &i);
Answer: Every thread receives the address of the same i, which keeps changing; pass a separate value per thread (an array element or the value cast to a pointer).
Coding Challenge
Split the sum of 1..100 000 000 across 4 threads (each sums a quarter into its own slot) and print the total.
#include <stdio.h>
#include <pthread.h>
enum { THREADS = 4 };
static const long N = 100000000L;
typedef struct { long from, to, sum; } Part;
static void *sum_part(void *arg)
{
Part *p = arg;
for (long i = p->from; i <= p->to; i++) p->sum += i;
return NULL;
}
int main(void)
{
pthread_t t[THREADS];
Part parts[THREADS];
long chunk = N / THREADS, total = 0;
for (int i = 0; i < THREADS; i++) {
parts[i] = (Part){i * chunk + 1, (i + 1) * chunk, 0};
pthread_create(&t[i], NULL, sum_part, &parts[i]);
}
for (int i = 0; i < THREADS; i++) { pthread_join(t[i], NULL); total += parts[i].sum; }
printf("sum 1..%ld = %ld (formula %ld)\n", N, total, N * (N + 1) / 2);
return 0;
}