DSA, Database System & Operating System β Operating System and Process Management, NEC licence examination syllabus (Nepal Engineering Council).
Threads; Processes versus Threads
Multiple execution paths inside one address space β cheaper to create, and dangerous because they share memory.
π Where this lives: the shared-memory property is both why threads are used and why concurrency bugs are so hard. Every browser tab used to be a thread; Chrome moved to one process per tab precisely because a crash or exploit in one thread takes down the whole address space. That decision cost memory (each process has its own copy of everything) and bought isolation. The same trade-off appears in every server design: Nginx uses few processes with event loops, Apache historically used a thread or process per request. Search "Chrome process per tab architecture" β the design document is a clear real-world statement of this exact trade.
What is shared and what is private
Threads within one process SHARE:
Β· the TEXT (code)
Β· the DATA segment (globals and statics)
Β· the HEAP (everything malloc'd)
Β· open file descriptors
Β· the current working directory
Β· signal handlers
Β· the process id and user id
Each thread has its OWN:
Β· thread id
Β· program counter
Β· register set
Β· STACK (its own call frames and locals)
Β· errno
Β· signal mask
Β· scheduling priority
THE KEY CONSEQUENCE: a global variable is shared, a local
variable is not. That single sentence explains most thread
bugs β and most fixes.
ββββββββββββββββ ONE PROCESS βββββββββββββββββ
β TEXT Β· DATA Β· HEAP Β· file descriptors β β SHARED
β β
β ββββββββββ ββββββββββ ββββββββββ β
β βthread 1β βthread 2β βthread 3β β
β β PC β β PC β β PC β β β PRIVATE
β β regs β β regs β β regs β β
β β stack β β stack β β stack β β
β ββββββββββ ββββββββββ ββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββ
WHY THREADS AT ALL β four reasons:
RESPONSIVENESS a UI thread stays interactive while a worker
thread loads data
RESOURCE SHARING no IPC needed; they already share memory
ECONOMY creating a thread is far cheaper than a
process (measured below)
SCALABILITY a single process can use all CPU cores
Measured β thread creation versus fork
cost.c
/* Verified on Darwin 25.5.0 arm64, 10 cores. */
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
#include <time.h>
#define N 300
static void *noop(void *a){ return NULL; }
static double now(void){ struct timespec ts;
clock_gettime(CLOCK_MONOTONIC,&ts);
return ts.tv_sec + ts.tv_nsec/1e9; }
int main(void){
double t0 = now();
for (int i=0;i<N;i++) {
pid_t p = fork();
if (p==0) _exit(0);
int s; waitpid(p,&s,0);
}
double tf = now()-t0;
t0 = now();
for (int i=0;i<N;i++) {
pthread_t t;
pthread_create(&t,NULL,noop,NULL);
pthread_join(t,NULL);
}
double tt = now()-t0;
printf("%d fork+wait : %.2f ms, %.1f us each\n",
N, tf*1000, tf*1e6/N);
printf("%d thread pairs: %.2f ms, %.1f us each\n",
N, tt*1000, tt*1e6/N);
printf("fork is %.1fx more expensive\n", tf/tt);
return 0;
}
MEASURED OUTPUT:
300 fork+wait : 106.92 ms total, 356.4 us each
300 thread pairs: 4.77 ms total, 15.9 us each
fork is 22.4x more expensive than thread creation
356 Β΅s versus 16 Β΅s β a 22Γ difference, measured.
WHY fork() COSTS MORE:
Β· a new PCB must be allocated and initialised
Β· the page tables must be COPIED (even with copy-on-write,
the table structure itself is duplicated)
Β· file descriptor tables are copied
Β· the new process needs its own address space identifier, so
the TLB is affected
WHY THREADS ARE CHEAP:
Β· no new address space β the page tables are reused
Β· only a stack and a small thread structure are allocated
Β· no file descriptor copying
COPY-ON-WRITE is why fork is only 22Γ and not 1000Γ: the
child's pages are marked read-only and shared; a physical
copy happens only when one side writes. Without COW, forking
a 1 GB process would copy 1 GB.
THE FULL COMPARISON:
PROCESS THREAD
creation cost 356 Β΅s (measured) 16 Β΅s (measured)
address space own SHARED
communication IPC needed shared memory
(pipes, sockets, (just use a global)
shared memory)
context switch expensive cheaper (no page
(page table swap) table change)
crash impact isolated KILLS ALL THREADS
data protection automatic you must add locks
debugging simpler races, deadlocks
THE TRADE, in one line: threads are cheap and unprotected;
processes are expensive and isolated.
The shared-memory hazard, measured
race.c
/* 8 threads each increment a shared counter 200,000 times.
Expected total: 1,600,000. Verified on this machine. */
#include <stdio.h>
#include <pthread.h>
#define THREADS 8
#define ITERS 200000
long unsafe_counter = 0;
long safe_counter = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *unsafe_fn(void *a){
for (long i=0;i<ITERS;i++) unsafe_counter++; /* RACE */
return NULL;
}
void *safe_fn(void *a){
for (long i=0;i<ITERS;i++) {
pthread_mutex_lock(&lock);
safe_counter++; /* critical section */
pthread_mutex_unlock(&lock);
}
return NULL;
}
MEASURED OUTPUT:
expected : 1600000
WITHOUT mutex : 1200000 (lost 400000 updates, 25.0%)
WITH mutex : 1600000 correct
400,000 updates LOST β a quarter of all the work β from code
that looks completely innocent.
WHY counter++ IS NOT ATOMIC. One C statement is three machine
operations:
LOAD r1 β counter read
ADD r1 β r1 + 1 modify
STORE counter β r1 write
Interleave two threads at the worst moment:
thread A: LOAD r1 β 100
thread B: LOAD r1 β 100 β reads the SAME value
thread A: ADD r1 = 101
thread B: ADD r1 = 101
thread A: STORE counter = 101
thread B: STORE counter = 101 β one increment LOST
Two increments happened; the counter advanced by one. This is
exactly the LOST UPDATE anomaly from the database
concurrency topic β the same bug at a different scale, which
is why the same solution (mutual exclusion) applies.
THE LOSS IS NOT A FIXED PERCENTAGE. Running the SAME binary
three more times on this machine:
WITHOUT mutex : 200000 (lost 1400000 β 87.5%)
WITHOUT mutex : 1400000 (lost 200000 β 12.5%)
WITHOUT mutex : 1600000 (lost 0 β 0.0%)
Read that third line carefully: the buggy program produced the
CORRECT answer. Nothing changed β same code, same machine, same
command. The interleaving simply happened not to collide.
THAT is the worst property of a race condition. It is not
"sometimes wrong"; it is wrong non-deterministically, so:
Β· it may pass every test you write
Β· it may pass a thousand runs and fail in production
Β· it may behave differently under a debugger (whose timing
differs), which is why these are called Heisenbugs
Β· a fix that "seems to work" may just have changed the
timing
The only reliable defence is to reason about the code rather
than to test for the symptom β or to use a tool that detects
the race directly rather than waiting for it to manifest:
clang -fsanitize=thread (ThreadSanitizer)
valgrind --tool=helgrind
Both flag the unsynchronised access even on a run that
produces the right answer.
THE FIX AND ITS COST: the mutex version is correct and
noticeably slower, because every increment now takes a lock.
The general lesson is to make critical sections as SHORT as
possible β or avoid sharing altogether:
Β· atomic operations (__atomic_fetch_add) for simple cases
Β· per-thread counters summed at the end (no sharing)
Β· immutable data (nothing to protect)
The measured 25% loss is the strongest argument in this topic. Nothing in counter++ looks dangerous, no compiler warns, and the program runs to completion producing a plausible-looking number. A bug that produces wrong output without failing is far more dangerous than one that crashes.
Thread implementation models
USER-LEVEL THREADS (many-to-one)
the thread library manages threads entirely in user space;
the kernel sees ONE thread.
β very fast switching β no system call needed
β works on an OS with no thread support
β ONE BLOCKING CALL BLOCKS EVERY THREAD (the kernel does not
know the others exist)
β cannot use multiple cores
examples: early Java green threads, GNU Portable Threads
KERNEL-LEVEL THREADS (one-to-one)
each user thread maps to one kernel thread.
β true parallelism across cores
β one thread blocking does not block the others
β creation and switching require system calls
β the kernel limits how many can exist
examples: Linux (pthreads/NPTL), Windows, macOS β this is
what the measurements above used
HYBRID (many-to-many)
m user threads multiplexed over n kernel threads.
β combines cheap creation with real parallelism
β complex to implement
examples: Solaris pre-9, Windows fibers, and β in a modern
form β Go's goroutines and Java 21's virtual threads
Go schedules thousands of goroutines onto a handful of OS
threads, and when a goroutine blocks on I/O the runtime parks
it and runs another on the same kernel thread. That is the
many-to-many model with the crucial fix for its historic
weakness: the runtime cooperates with the kernel rather than
hiding from it.
MULTITHREADING MODELS SUMMARY:
many-to-one cheap, no parallelism, blocking is fatal
one-to-one real parallelism, costlier, what Linux does
many-to-many best of both, hardest to build
π Go further: the industry has partly moved past OS threads for I/O-bound work, because one-to-one threads cost ~8 KB of stack each and a server handling 100,000 connections cannot afford 800 MB of stacks. The answer is async/await (Python asyncio, Rust tokio, JavaScript) or green threads with a smart runtime (Go goroutines at ~2 KB, Java 21 virtual threads). Both are the many-to-one and many-to-many models returning with the blocking problem solved. Search "C10K problem" for the original framing and "Java virtual threads Loom" for the newest answer.
π‘ Exam angle: the shared versus private table is the core answer β code, data, heap and file descriptors shared; PC, registers and stack private. Give the four benefits (responsiveness, resource sharing, economy, scalability) and the process/thread comparison on cost, isolation, communication and crash impact. Know the three multithreading models and, crucially, that many-to-one suffers because one blocking call blocks all threads. Explaining why counter++ is not atomic (load-modify-store) earns marks in both this topic and the synchronisation topics.
Syllabus points
Threads (user/kernel)
Process vs thread
Create a free account to tick topics off, take notes as you read, watch the video lessons and get a day-by-day study plan built around your exam date.
Related topics in Operating System and Process Management