Mastering POSIX Threads in Linux: A Complete Guide to Pthreads
This article explains the limitations of the traditional Unix fork model, introduces POSIX threads as lightweight processes, details their data structures, core functions, creation, termination, synchronization primitives, attributes, and provides extensive C code examples for practical multithreaded programming on Linux.
Introduction:
In the traditional Unix model, a process that needs another entity to perform a task forks a child process to handle it. Most Unix network servers are written this way: the parent accepts connections, forks a child, and the child interacts with the client. Although this model works well, forking has drawbacks: it is expensive because the memory image must be copied, and inter‑process communication (IPC) is required to exchange information between parent and child.
Threads help solve these problems. Often called lightweight processes, threads are 10–100 times faster to create than processes. All threads in a process share the same global memory, making data sharing easy but introducing synchronization challenges. Threads share process‑wide resources such as open files, signal handlers, working directory, and user IDs, while each thread has its own ID, registers, stack, error number, signal mask, and priority.
In Linux, thread programming follows the POSIX.1 standard and is called Pthreads. All pthread functions start with pthread_ and require including pthread.h and linking with -lpthread.
1. Thread Basics
Data structures:
pthread_t // thread ID
pthread_attr_t // thread attributesOperation functions:
pthread_create() // create a thread
pthread_exit() // terminate current thread
pthread_cancel() // cancel another thread
pthread_join() // wait for a thread to finish
pthread_attr_init()
pthread_attr_setdetachstate()
pthread_attr_getdetachstate()
pthread_attr_destroy()
pthread_kill()Synchronization functions:
// mutex and condition variable functions
pthread_mutex_init()
pthread_mutex_destroy()
pthread_mutex_lock()
pthread_mutex_trylock()
pthread_mutex_unlock()
pthread_cond_init()
pthread_cond_destroy()
pthread_cond_signal()
pthread_cond_wait()
// thread‑specific data (TLS)
pthread_key_create()
pthread_setspecific()
pthread_getspecific()
pthread_key_delete()
pthread_attr_getschedparam()
pthread_attr_setschedparam()2. Concepts
Thread components include Thread ID, Stack, Policy (priority), Signal mask, Errno, and Thread‑Specific Data.
3. Thread Definition
Thread identifier type:
pthread_t pthread_ID; // implementation‑defined typeEquality check:
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);Get current thread ID:
#include <pthread.h>
pthread_t pthread_self(void);4. Thread Creation
Creating a thread with pthread_create:
#include <pthread.h>
int pthread_create(
pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void *),
void *restrict arg);Parameters: pthread_t *restrict tidp: returns the created thread’s ID. const pthread_attr_t *restrict attr: thread attributes (NULL for defaults). void *(*start_rtn)(void *): function the thread will run. void *restrict arg: argument passed to the start routine.
If creation succeeds, the thread ID is stored via tidp. Attributes can specify stack size, priority, detach state, etc.; otherwise defaults are used.
5. Thread Exit
Process‑wide exit functions ( exit, _Exit, _exit) terminate the whole process, not a single thread.
Threads can terminate by:
Returning from the start routine.
Being cancelled by another thread.
Calling pthread_exit.
Usage of pthread_exit and pthread_join:
#include <pthread.h>
void pthread_exit(void *rval_ptr);
int pthread_join(pthread_t thread, void **rval_ptr); pthread_joinblocks until the specified thread terminates and retrieves its return value. It is analogous to waitpid for processes.
6. Thread Cancellation
Cancel a thread:
#include <pthread.h>
void pthread_cancel(pthread_t tid);The target thread receives a cancellation request, which it can act upon or ignore depending on its cancellation state.
7. Cleanup Handlers
Register cleanup functions that run when a thread exits or is cancelled:
#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);These functions maintain a stack of cleanup handlers; they must be called in the same lexical scope.
8. Process vs. Thread Primitives
Process Primitive
Thread Primitive
Description
fork
pthread_create
Creates a new flow of control
exit
pthread_exit
Exits an existing flow of control
waitpid
pthread_join
Waits for a flow to finish and obtains its exit code
atexit
pthread_cleanup_push
Registers a function to be called on flow exit
getpid
pthread_self
Gets the flow’s ID
abort
pthread_cancel
Requests abnormal termination
9. Thread Attributes
Thread attributes are managed via pthread_attr_t. After initializing with pthread_attr_init, you can set binding, detach state, stack address/size, and priority. By default, threads are non‑bound, joinable, inherit the parent’s stack and priority.
Binding (affinity) ties a thread to a specific lightweight process (LWP). Use pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM) for a bound thread.
#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_create(&tid, &attr, (void *)my_function, NULL);Detach state determines whether a thread’s resources are reclaimed automatically ( PTHREAD_CREATE_DETACHED) or must be joined ( PTHREAD_CREATE_JOINABLE).
#include <pthread.h>
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);Priority is stored in a sched_param structure and set with pthread_attr_getschedparam / pthread_attr_setschedparam:
#include <pthread.h>
#include <sched.h>
pthread_attr_t attr;
sched_param param;
int newprio = 20;
pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr, ¶m);
param.sched_priority = newprio;
pthread_attr_setschedparam(&attr, ¶m);10. Thread Synchronization
Mutexes ( pthread_mutex_t) provide exclusive access to critical sections. Initialize with pthread_mutex_init, lock with pthread_mutex_lock (blocking) or pthread_mutex_trylock (non‑blocking), and unlock with pthread_mutex_unlock. Destroy with pthread_mutex_destroy.
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);Reader‑Writer locks ( pthread_rwlock_t) allow multiple concurrent readers or a single writer.
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);Condition variables ( pthread_cond_t) let a thread wait for a condition while holding a mutex. Initialize with pthread_cond_init, wait with pthread_cond_wait (or pthread_cond_timedwait), signal with pthread_cond_signal (single thread) or pthread_cond_broadcast (multiple threads), and destroy with pthread_cond_destroy.
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);Example of using a mutex and condition variable to synchronize a producer‑consumer pair:
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
void decrement_count(){
pthread_mutex_lock(&count_lock);
while(count == 0)
pthread_cond_wait(&count_nonzero, &count_lock);
count--;
pthread_mutex_unlock(&count_lock);
}
void increment_count(){
pthread_mutex_lock(&count_lock);
if(count == 0)
pthread_cond_signal(&count_nonzero);
count++;
pthread_mutex_unlock(&count_lock);
}The timespec structure used for timed waits is defined as:
struct timespec {
time_t tv_sec; // seconds
long tv_nsec; // nanoseconds
};Note that timespec values represent absolute time; you typically obtain the current time with gettimeofday and add an offset.
Images illustrating the concepts:
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
