Pthreads Visual Guide (Linux/Unix)
A visual, analogy driven cheat sheet for thread lifecycle, sync, pitfalls, and memory layout.
Goal: Understand how Pthreads work, use them safely, and remember concepts via visuals.
Legend (symbols/styles): Flow
Thread / Function Critical Section Shared Resource
Analogies:
- Mutex = single bathroom key: only one person can enter at a time.
- Condition variable = doorbell: wait until someone rings to proceed.
- Semaphore = parking lot counter: N cars allowed; others queue until a spot free
What you'll see:
Lifecycle: create run sync finish
Sync mechanisms: mutex, condition variables, semaphore (vs mutex)
Shared data & critical sections
Pitfalls: deadlock, race condition
Execution flow & memory map
Creation
Pthread Lifecycle
Execution Synchronization Termination
Do other work
Main Thread Timeline
pthread_create() (or thread management) pthread_join()
Start routine Work (may lock,
Workervoid*
Thread Timeline
(*fn)(void*) wait, signal) pthread_exit()
Notes:
pthread_create returns immediately in the caller; the new thread begins at
the start routine.
Synchronization (mutex/condvar) prevents data races while threads run in
parallel.
pthread_join waits for a thread to finish and optionally collects its return
value.
Mutex Mutual Exclusion
Only one thread enters the critical section at a time (the bathroom key).
Thread A Thread B
Shared Resource
(e.g., queue, counter)
Critical Section
(protected by mutex)
pthread_mutex_t m
Pattern:
pthread_mutex_lock(&m); // enter critical section
/* access/modify shared resource */
pthread_mutex_unlock(&m); // leave critical section
Tips to remember:
Keep critical sections short to reduce contention.
Always pair lock/unlock in the same function/scope if possible.
Prefer one mutex per independent resource.
Condition Variables Wait/Signal
Doorbell model: sleepers wait until someone signals the condition is true.
Producer Consumer
Bounded Buffer
(items 0..N-1)
Mutex m
Producer loop: Consumer loop:
lock(m) cond not_empty cond not_full lock(m)
while buffer full: wait(not_full, m) while buffer empty: wait(not_empty, m)
put item get item
signal(not_empty) signal(not_full)
unlock(m) unlock(m)
Remember:
Always wait in a loop (spurious wakeups / re-check predicate).
wait(cond, m) releases m while sleeping and re-acquires it on wake.
Semaphores vs Mutex High Level
Parking lot counter vs single bathroom key.
View
Mutex Semaphore (counting)
Ownership: lock is held by the thread Counter 0 controls concurrent access
that acquired it; unlock by same thread.P(): decrement/wait; V(): increment/signal.
Counting (0..N). Ideal for N identical resources
Binary (0/1). Ideal for critical sections. (e.g., buffer slots, connections).
Priority inversion risk; use priority protocols
No strict ownership; avoid misuse as a mutex
if RT constraints apply. when single-owner semantics are needed.
Analogy:
Semaphore = parking lot with N spots; cars wait when full.
Mutex = one bathroom key; only one person at a time.
Shared Data & Critical Sections
Protect shared reads/writes with short, well scoped locks.
Thread A struct Shared { Thread B
(producer) int count; (consumer)
queue items;
};
Critical Section
(lock m around count/items)
mutex m
Best Practices:
Clearly mark critical sections; keep them minimal.
Avoid calling blocking I/O while holding a mutex.
Prefer immutable data for readers (copy-on-write / snapshots).
Document which mutex protects which fields.
Deadlock Circular Wait
Classic: two threads, two locks each holds one, waits for the other.
Lock A Lock B
Thread 1 Thread 2
holds A waits B holds B waits A
Prevent/avoid:
Impose a global lock order: always acquire A before B.
Use trylock with backoff to break cycles.
Hold locks for minimal time; avoid nested locking when possible.
Race Condition Non Atomic Update
Outcome depends on unlucky interleavings without proper synchronization.
Two threads increment a shared counter:
Expected final value: +2; Actual (racy) may be +1.
read x (=10) x+1 (=11) write 11
T1
read x (=10) x+1 (=11) write 11
T2
Fix:
Protect the increment with a mutex OR use atomic operations.
lock(m); x++; unlock(m); // or atomic_fetch_add()
ExecutionHighFlow Multithreaded
level flowchart of a typical Pthreads app.
Program
Start main()
Init shared data
init mutex/cond
Create threads (N)
pthread_create
Threads run start routines
(do work, sync)
Join threads
pthread_join
Cleanup
destroy mutex/cond
Return/Exit
Tip: Encapsulate shared state + mutex in a struct passed to threads.
MemoryShared
Map Process + Threads
heap; per thread stacks + TLS.
Text/RO Data (shared) Globals (shared)
Heap (shared across threads)
(malloc/new)
Thread 1 Stack Thread 2 Stack
Process Address Space
Thread 3 Stack
Thread Local Storage (TLS) Thread Local Storage (TLS)
__thread / pthread_key_t per thread variables
Remember:
Heap and globals are shared protect mutations.
Each thread has its own stack and TLS.