POSIX Threads (pthreads) Tutorial
Overview
POSIX threads (pthreads) provide a standardized API for creating and managing threads within a process. Threads share the same memory space but execute independently.
Thread Architecture
┌─────────────────────────────────────────────────────────┐
│ Process │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Shared Resources │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Code Segment (Text) │ │ │
│ │ │ Data Segment (Global/Static variables) │ │ │
│ │ │ Heap (malloc allocations) │ │ │
│ │ │ File Descriptors │ │ │
│ │ │ Signal Handlers │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ Thread 1 Thread 2 Thread 3 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Stack │ │ Stack │ │ Stack │ │
│ │ Registers│ │ Registers│ │ Registers│ │
│ │ PC │ │ PC │ │ PC │ │
│ │ TID │ │ TID │ │ TID │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ▼ ▼ ▼ │
│ [Running] [Ready] [Blocked] │
└─────────────────────────────────────────────────────────┘
Threads vs Processes:
┌────────────────────┬──────────────────┬──────────────────┐
│ Feature │ Thread │ Process │
├────────────────────┼──────────────────┼──────────────────┤
│ Memory Space │ Shared │ Separate │
│ Creation Overhead │ Low │ High │
│ Context Switch │ Fast │ Slow │
│ Communication │ Easy (memory) │ IPC needed │
│ Isolation │ None │ Strong │
│ Resource Usage │ Light │ Heavy │
└────────────────────┴──────────────────┴──────────────────┘
1. Thread Creation and Termination
pthread_create() - Create Thread
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg);
Basic Example
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *thread_function(void *arg) {
int id = *(int *)arg;
printf("Thread %d: Hello from thread!\n", id);
sleep(1);
printf("Thread %d: Exiting\n", id);
return NULL;
}
int main() {
pthread_t thread;
int thread_id = 1;
if (pthread_create(&thread, NULL, thread_function, &thread_id) != 0) {
perror("pthread_create");
return 1;
}
printf("Main: Created thread\n");
// Wait for thread to finish
pthread_join(thread, NULL);
printf("Main: Thread completed\n");
return 0;
}
Multiple Threads
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5
void *worker(void *arg) {
long id = (long)arg;
printf("Thread %ld starting\n", id);
sleep(1);
printf("Thread %ld finishing\n", id);
return (void *)(id * 2);
}
int main() {
pthread_t threads[NUM_THREADS];
// Create threads
for (long i = 0; i < NUM_THREADS; i++) {
if (pthread_create(&threads[i], NULL, worker, (void *)i) != 0) {
perror("pthread_create");
return 1;
}
}
// Wait for all threads
for (int i = 0; i < NUM_THREADS; i++) {
void *result;
pthread_join(threads[i], &result);
printf("Thread %d returned: %ld\n", i, (long)result);
}
return 0;
}
pthread_exit() - Terminate Thread
#include <pthread.h>
void *thread_function(void *arg) {
printf("Thread doing work\n");
// Exit thread with return value
pthread_exit((void *)42);
// This code is never reached
printf("Never printed\n");
return NULL;
}
pthread_join() - Wait for Thread
#include <pthread.h>
int main() {
pthread_t thread;
void *result;
pthread_create(&thread, NULL, thread_function, NULL);
// Block until thread terminates
if (pthread_join(thread, &result) != 0) {
perror("pthread_join");
return 1;
}
printf("Thread returned: %ld\n", (long)result);
return 0;
}
pthread_detach() - Detached Thread
#include <pthread.h>
void *detached_thread(void *arg) {
printf("Detached thread running\n");
sleep(2);
printf("Detached thread exiting\n");
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, detached_thread, NULL);
// Detach thread - no need to join
pthread_detach(thread);
printf("Main continuing without waiting\n");
sleep(3); // Keep main alive
return 0;
}
2. Thread Synchronization - Mutexes
pthread_mutex_t - Mutual Exclusion Lock
Without Mutex (Race Condition):
Thread 1 Thread 2
read counter (0) read counter (0)
increment (1) increment (1)
write counter (1) write counter (1)
Result: counter = 1 (should be 2!)
With Mutex:
Thread 1 Thread 2
lock mutex [blocked waiting]
read counter (0) [waiting...]
increment (1) [waiting...]
write counter (1) [waiting...]
unlock mutex lock mutex
read counter (1)
increment (2)
write counter (2)
unlock mutex
Result: counter = 2 (correct!)
Basic Mutex Example
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;
void *increment(void *arg) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&mutex);
shared_counter++;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, increment, NULL);
pthread_create(&t2, NULL, increment, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Counter: %d (expected: 200000)\n", shared_counter);
pthread_mutex_destroy(&mutex);
return 0;
}
Dynamic Mutex Initialization
#include <pthread.h>
pthread_mutex_t mutex;
int main() {
// Initialize mutex
if (pthread_mutex_init(&mutex, NULL) != 0) {
perror("pthread_mutex_init");
return 1;
}
// Use mutex
pthread_mutex_lock(&mutex);
// Critical section
pthread_mutex_unlock(&mutex);
// Destroy mutex
pthread_mutex_destroy(&mutex);
return 0;
}
Trylock and Timedlock
#include <pthread.h>
#include <time.h>
#include <errno.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *thread_func(void *arg) {
// Try to lock without blocking
if (pthread_mutex_trylock(&mutex) == 0) {
printf("Lock acquired\n");
// Critical section
pthread_mutex_unlock(&mutex);
} else {
printf("Lock busy, skipping\n");
}
// Timed lock (5 seconds)
struct timespec timeout;
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += 5;
int ret = pthread_mutex_timedlock(&mutex, &timeout);
if (ret == 0) {
printf("Timed lock acquired\n");
pthread_mutex_unlock(&mutex);
} else if (ret == ETIMEDOUT) {
printf("Timed out waiting for lock\n");
}
return NULL;
}
3. Condition Variables
pthread_cond_t - Thread Synchronization
Condition Variable Pattern:
Producer Thread:
lock(mutex)
add_item_to_buffer()
signal(cond) ← Wake up waiting consumer
unlock(mutex)
Consumer Thread:
lock(mutex)
while (buffer_empty())
wait(cond, mutex) ← Release mutex and sleep
← When signaled, reacquire mutex
remove_item_from_buffer()
unlock(mutex)
Producer-Consumer with Condition Variables
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 10
int buffer[BUFFER_SIZE];
int count = 0;
int in = 0;
int out = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
void *producer(void *arg) {
for (int i = 0; i < 20; i++) {
pthread_mutex_lock(&mutex);
// Wait while buffer is full
while (count == BUFFER_SIZE) {
pthread_cond_wait(¬_full, &mutex);
}
// Produce item
buffer[in] = i;
in = (in + 1) % BUFFER_SIZE;
count++;
printf("Produced: %d (count: %d)\n", i, count);
// Signal consumer
pthread_cond_signal(¬_empty);
pthread_mutex_unlock(&mutex);
usleep(100000);
}
return NULL;
}
void *consumer(void *arg) {
for (int i = 0; i < 20; i++) {
pthread_mutex_lock(&mutex);
// Wait while buffer is empty
while (count == 0) {
pthread_cond_wait(¬_empty, &mutex);
}
// Consume item
int item = buffer[out];
out = (out + 1) % BUFFER_SIZE;
count--;
printf("Consumed: %d (count: %d)\n", item, count);
// Signal producer
pthread_cond_signal(¬_full);
pthread_mutex_unlock(&mutex);
usleep(150000);
}
return NULL;
}
int main() {
pthread_t prod, cons;
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(¬_empty);
pthread_cond_destroy(¬_full);
return 0;
}
Broadcast vs Signal
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int ready = 0;
void *waiter(void *arg) {
pthread_mutex_lock(&mutex);
while (!ready) {
pthread_cond_wait(&cond, &mutex);
}
printf("Thread %ld woke up\n", (long)arg);
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t threads[5];
// Create waiting threads
for (long i = 0; i < 5; i++) {
pthread_create(&threads[i], NULL, waiter, (void *)i);
}
sleep(1);
pthread_mutex_lock(&mutex);
ready = 1;
// pthread_cond_signal(&cond); // Wakes ONE thread
pthread_cond_broadcast(&cond); // Wakes ALL threads
pthread_mutex_unlock(&mutex);
for (int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
4. Read-Write Locks
pthread_rwlock_t - Multiple Readers, Single Writer
Read-Write Lock Behavior:
State: Unlocked
Reader 1 locks → State: Read locked (count=1)
Reader 2 locks → State: Read locked (count=2) ✓ Allowed
Writer waits → BLOCKED
Reader 3 locks → State: Read locked (count=3) ✓ Allowed
Reader 1 unlocks → State: Read locked (count=2)
Reader 2 unlocks → State: Read locked (count=1)
Reader 3 unlocks → State: Unlocked
Writer locks → State: Write locked ✓ Exclusive
┌────────────────┬──────────────┬──────────────┐
│ Lock State │ Reader │ Writer │
├────────────────┼──────────────┼──────────────┤
│ Unlocked │ Grant │ Grant │
│ Read Locked │ Grant │ Block │
│ Write Locked │ Block │ Block │
└────────────────┴──────────────┴──────────────┘
Example
#include <pthread.h>
#include <stdio.h>
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int shared_data = 0;
void *reader(void *arg) {
long id = (long)arg;
for (int i = 0; i < 5; i++) {
pthread_rwlock_rdlock(&rwlock);
printf("Reader %ld: data = %d\n", id, shared_data);
usleep(100000);
pthread_rwlock_unlock(&rwlock);
usleep(50000);
}
return NULL;
}
void *writer(void *arg) {
long id = (long)arg;
for (int i = 0; i < 3; i++) {
pthread_rwlock_wrlock(&rwlock);
shared_data++;
printf("Writer %ld: wrote %d\n", id, shared_data);
usleep(200000);
pthread_rwlock_unlock(&rwlock);
usleep(100000);
}
return NULL;
}
int main() {
pthread_t readers[3], writers[2];
// Create writers
for (long i = 0; i < 2; i++) {
pthread_create(&writers[i], NULL, writer, (void *)i);
}
// Create readers
for (long i = 0; i < 3; i++) {
pthread_create(&readers[i], NULL, reader, (void *)i);
}
// Wait for all
for (int i = 0; i < 2; i++) {
pthread_join(writers[i], NULL);
}
for (int i = 0; i < 3; i++) {
pthread_join(readers[i], NULL);
}
pthread_rwlock_destroy(&rwlock);
return 0;
}
Trylock and Timedlock
#include <pthread.h>
#include <time.h>
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
void *thread_func(void *arg) {
// Try read lock
if (pthread_rwlock_tryrdlock(&rwlock) == 0) {
printf("Read lock acquired\n");
pthread_rwlock_unlock(&rwlock);
}
// Try write lock
if (pthread_rwlock_trywrlock(&rwlock) == 0) {
printf("Write lock acquired\n");
pthread_rwlock_unlock(&rwlock);
}
// Timed read lock
struct timespec timeout;
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += 5;
if (pthread_rwlock_timedrdlock(&rwlock, &timeout) == 0) {
printf("Timed read lock acquired\n");
pthread_rwlock_unlock(&rwlock);
}
return NULL;
}
5. Barriers
pthread_barrier_t - Synchronization Point
Barrier Synchronization:
Thread 1: ──────────────────────►├─ barrier ─┤► Continue
Thread 2: ──────►├─ barrier ─┤ (wait) ├► Continue
Thread 3: ────────────►├─ barrier ─┤ (wait) ├► Continue
All threads must reach barrier before any can continue
Example
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5
pthread_barrier_t barrier;
void *worker(void *arg) {
long id = (long)arg;
printf("Thread %ld: Phase 1\n", id);
sleep(id); // Different work times
// Wait for all threads to complete phase 1
printf("Thread %ld: Waiting at barrier\n", id);
pthread_barrier_wait(&barrier);
printf("Thread %ld: Phase 2 (all synchronized)\n", id);
return NULL;
}
int main() {
pthread_t threads[NUM_THREADS];
// Initialize barrier for NUM_THREADS threads
pthread_barrier_init(&barrier, NULL, NUM_THREADS);
for (long i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, worker, (void *)i);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
pthread_barrier_destroy(&barrier);
return 0;
}
6. Spin Locks
pthread_spinlock_t - Busy-Wait Lock
Mutex vs Spinlock:
Mutex (blocks thread):
Thread tries lock
Lock busy → Thread sleeps (context switch)
Lock available → Wake up thread (context switch)
Good for: Long critical sections
Spinlock (busy-wait):
Thread tries lock
Lock busy → Thread spins in loop checking lock
Lock available → Acquire immediately
Good for: Very short critical sections, real-time systems
Example
#include <pthread.h>
#include <stdio.h>
pthread_spinlock_t spinlock;
int counter = 0;
void *increment(void *arg) {
for (int i = 0; i < 100000; i++) {
pthread_spin_lock(&spinlock);
counter++;
pthread_spin_unlock(&spinlock);
}
return NULL;
}
int main() {
pthread_t t1, t2;
// PTHREAD_PROCESS_PRIVATE: only within process
// PTHREAD_PROCESS_SHARED: shared between processes
pthread_spin_init(&spinlock, PTHREAD_PROCESS_PRIVATE);
pthread_create(&t1, NULL, increment, NULL);
pthread_create(&t2, NULL, increment, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Counter: %d\n", counter);
pthread_spin_destroy(&spinlock);
return 0;
}
7. Thread-Local Storage
pthread_key_t - Thread-Specific Data
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_key_t key;
void destructor(void *value) {
printf("Destructor called for value: %ld\n", (long)value);
// Free allocated memory if needed
}
void *thread_func(void *arg) {
long id = (long)arg;
// Set thread-specific value
pthread_setspecific(key, (void *)(id * 100));
// Get thread-specific value
long value = (long)pthread_getspecific(key);
printf("Thread %ld: my value is %ld\n", id, value);
return NULL;
}
int main() {
pthread_t threads[3];
// Create key with destructor
pthread_key_create(&key, destructor);
for (long i = 0; i < 3; i++) {
pthread_create(&threads[i], NULL, thread_func, (void *)i);
}
for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}
pthread_key_delete(key);
return 0;
}
Using __thread Keyword (GCC Extension)
#include <pthread.h>
#include <stdio.h>
__thread int thread_local_var = 0;
void *thread_func(void *arg) {
long id = (long)arg;
thread_local_var = id * 10;
printf("Thread %ld: my var = %d\n", id, thread_local_var);
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_func, (void *)1);
pthread_create(&t2, NULL, thread_func, (void *)2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Main: my var = %d\n", thread_local_var);
return 0;
}
8. Thread Attributes
pthread_attr_t - Customize Thread Behavior
#include <pthread.h>
#include <stdio.h>
void *thread_func(void *arg) {
printf("Thread running\n");
return NULL;
}
int main() {
pthread_t thread;
pthread_attr_t attr;
// Initialize attributes
pthread_attr_init(&attr);
// Set detached state
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// Set stack size (2MB)
size_t stacksize = 2 * 1024 * 1024;
pthread_attr_setstacksize(&attr, stacksize);
// Set scheduling policy
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
// Set priority
struct sched_param param;
param.sched_priority = 10;
pthread_attr_setschedparam(&attr, ¶m);
// Set scope
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
// Create thread with attributes
if (pthread_create(&thread, &attr, thread_func, NULL) != 0) {
perror("pthread_create");
return 1;
}
// Destroy attributes
pthread_attr_destroy(&attr);
// No need to join (detached)
sleep(1);
return 0;
}
9. Thread Cancellation
pthread_cancel() - Terminate Thread
#include <pthread.h>
#include <stdio.h>
void cleanup_handler(void *arg) {
printf("Cleanup: %s\n", (char *)arg);
}
void *thread_func(void *arg) {
// Push cleanup handler
pthread_cleanup_push(cleanup_handler, "Resource freed");
// Set cancellation type
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
for (int i = 0; i < 10; i++) {
printf("Thread: iteration %d\n", i);
sleep(1);
// Cancellation point
pthread_testcancel();
}
// Pop cleanup handler (execute=0: don't execute now)
pthread_cleanup_pop(0);
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, thread_func, NULL);
sleep(3);
// Cancel thread
printf("Main: Canceling thread\n");
pthread_cancel(thread);
pthread_join(thread, NULL);
printf("Main: Thread canceled\n");
return 0;
}
10. Thread Safety and Synchronization Patterns
Once Initialization
#include <pthread.h>
#include <stdio.h>
pthread_once_t once_control = PTHREAD_ONCE_INIT;
void init_routine(void) {
printf("Initialization executed once\n");
// Initialize shared resources
}
void *thread_func(void *arg) {
// This ensures init_routine is called exactly once
pthread_once(&once_control, init_routine);
printf("Thread %ld running\n", (long)arg);
return NULL;
}
int main() {
pthread_t threads[5];
for (long i = 0; i < 5; i++) {
pthread_create(&threads[i], NULL, thread_func, (void *)i);
}
for (int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
Thread-Safe Singleton Pattern
#include <pthread.h>
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static int *singleton = NULL;
int *get_singleton() {
if (singleton == NULL) {
pthread_mutex_lock(&mutex);
if (singleton == NULL) { // Double-checked locking
singleton = malloc(sizeof(int));
*singleton = 42;
}
pthread_mutex_unlock(&mutex);
}
return singleton;
}
Compilation
# Compile with pthread library
gcc -pthread -o program program.c
# Or explicitly link
gcc -o program program.c -lpthread
# With debugging and warnings
gcc -pthread -Wall -Wextra -g -o program program.c
Common Pitfalls
- Race Conditions - Always protect shared data with mutexes
- Deadlocks - Acquire locks in consistent order
- Forgetting to Join - Causes resource leaks
- Not Using Condition Variables - Busy-waiting wastes CPU
- Mixing Processes and Threads - fork() with threads is dangerous
- Signal Handling - Signals in multithreaded programs are complex
Best Practices
1. ✓ Initialize mutexes before use
2. ✓ Always unlock what you lock
3. ✓ Use condition variables for waiting
4. ✓ Join or detach all threads
5. ✓ Minimize critical section size
6. ✓ Use thread-safe functions (e.g., strtok_r vs strtok)
7. ✓ Handle pthread function return values
8. ✓ Use barriers for phase synchronization
9. ✓ Consider read-write locks for reader-heavy workloads
10. ✓ Test thoroughly for race conditions
See Also
man pthreads- Overviewman pthread_createman pthread_mutex_lockman pthread_cond_wait- Complete examples