Fundamentals 22 min read

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.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Mastering POSIX Threads in Linux: A Complete Guide to Pthreads

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 attributes

Operation 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 type

Equality 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_join

blocks 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, &param);
param.sched_priority = newprio;
pthread_attr_setschedparam(&attr, &param);

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:

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

C programmingthreadingpthreadsPOSIX
MaGe Linux Operations
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.