An Introduction to POSIX Threads (Pthread) and Their Practical Usage in C/C++
This article provides a comprehensive overview of POSIX threads, covering their architecture, creation, synchronization, attributes, scheduling policies, advantages, drawbacks, and real‑world examples such as mutex protection, barrier synchronization, and a cross‑platform network server implementation.
In the world of programming, multithreading acts like a magical key that unlocks efficient processing and resource utilization, and Pthread (POSIX threads) stands out as a brilliant gem for implementing multithreaded applications on Linux, Unix, macOS, and even Windows via compatibility layers.
1. Introduction to Pthread
Pthread is the POSIX standard thread library that offers a powerful and portable set of APIs for creating, managing, and synchronizing threads across many operating systems, including Unix‑like systems and Windows (through ports such as pthreads‑win32).
The API follows familiar C/C++ naming conventions; for example, pthread_create creates a new thread and takes parameters for thread ID, attributes, start routine, and arguments, allowing fine‑grained control over thread creation.
2. Specific Usage of Pthread
2.1 Creating Threads
The primary way to create a thread is via pthread_create . Its prototype is:
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);Example code:
#include
#include
#include
#include
#include
pthread_t ntid;
void printids(const char *s){
pid_t pid = getpid();
pthread_t tid = pthread_self();
printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);
}
void *thr_fn(void *arg){
printids("new thread: ");
return NULL;
}
int main(void){
int err = pthread_create(&ntid, NULL, thr_fn, NULL);
if (err != 0) printf("can't create thread: %s\n", strerror(err));
printids("main thread:");
sleep(1);
exit(0);
}2.2 Thread Blocking and Exit
pthread_join blocks the calling thread until the specified thread terminates, returning the thread’s exit status:
#include
#include
void* thread_main(void* pmax){
int max = *((int*)pmax);
for(int i=0;ipthread_exit terminates the calling thread and makes its argument available to a joining thread.
2.3 Obtaining Thread Identifiers
pthread_self returns the calling thread’s ID, useful for distinguishing threads in concurrent code.
#include
#include
#include
#include
#include
pthread_t ntid;
void printids(const char *s){
pid_t pid = getpid();
pthread_t tid = pthread_self();
printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);
}
int main(void){
printids("main thread:");
return 0;
}3. Pthread Thread Attributes
3.1 Detach State
Threads are joinable by default; they can be created as detached either by setting the attribute before pthread_create or by calling pthread_detach after creation.
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, THREAD_FUNCTION, arg);Or:
pthread_t thread_id;
pthread_create(&thread_id, NULL, thread_function, NULL);
pthread_detach(thread_id);3.2 Priority Adjustment
Thread priority can be set with pthread_attr_setschedparam when using real‑time scheduling policies (SCHED_FIFO or SCHED_RR). The priority range can be queried with sched_get_priority_min and sched_get_priority_max .
pthread_attr_t attr;
struct sched_param param;
pthread_attr_init(&attr);
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
int min = sched_get_priority_min(SCHED_RR);
int max = sched_get_priority_max(SCHED_RR);
param.sched_priority = (min + max) / 2;
pthread_attr_setschedparam(&attr, ¶m);3.3 Scheduling Policies
Pthread supports three policies: SCHED_OTHER (default time‑sharing), SCHED_FIFO (real‑time first‑in‑first‑out), and SCHED_RR (real‑time round‑robin). The policy is set via pthread_attr_setschedpolicy .
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setschedpolicy(&attr, SCHED_RR);4. Advantages and Disadvantages of Pthread
4.1 Advantages
Cross‑platform portability, tight integration with C/C++, low‑level control over thread resources, and widespread support on modern Unix‑like systems.
4.2 Disadvantages
Lack of built‑in read‑write locks, a relatively small API surface compared to higher‑level threading libraries, and the need for extra code to implement some advanced features.
5. Application Scenarios
5.1 Mutex Protection Example
Simulating multiple ticket‑selling windows, a mutex ensures that ticket count updates are atomic.
#include
#include
#include
int tickets = 100;
pthread_mutex_t mutex;
void* sellTickets(void* arg){
while(tickets > 0){
pthread_mutex_lock(&mutex);
if(tickets > 0){
printf("Window %ld sold a ticket, remaining %d\n", pthread_self(), --tickets);
}
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main(){
pthread_mutex_init(&mutex, NULL);
pthread_t threads[5];
for(int i=0;i<5;i++) pthread_create(&threads[i], NULL, sellTickets, NULL);
for(int i=0;i<5;i++) pthread_join(threads[i], NULL);
pthread_mutex_destroy(&mutex);
return 0;
}5.2 Barrier Synchronization Example
Using pthread_barrier to synchronize three worker threads after they finish processing their data.
#include
#include
#include
pthread_barrier_t barrier;
void* processData(void* arg){
int id = *(int*)arg;
printf("Thread %d processing...\n", id);
sleep(id + 1);
printf("Thread %d finished, waiting...\n", id);
pthread_barrier_wait(&barrier);
if(id == 0) printf("All threads finished, starting aggregation...\n");
return NULL;
}
int main(){
pthread_t threads[3];
int ids[3] = {0,1,2};
pthread_barrier_init(&barrier, NULL, 3);
for(int i=0;i<3;i++) pthread_create(&threads[i], NULL, processData, &ids[i]);
for(int i=0;i<3;i++) pthread_join(threads[i], NULL);
pthread_barrier_destroy(&barrier);
return 0;
}5.3 Cross‑Platform Network Server Example
A simple server that accepts client connections and spawns a new thread for each client, compiling on both Linux (using -pthread ) and Windows (using pthreads‑win32 ).
#include
#include
#include
#include
#include
#ifdef _WIN32
#include
#else
#include
#include
#endif
void* handleClient(void* arg){
// client handling code
return NULL;
}
int main(){
int serverSocket;
struct sockaddr_in serverAddress, clientAddress;
socklen_t clientAddressLength;
pthread_t thread;
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0){
perror("WSAStartup failed");
return -1;
}
#endif
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == -1){ perror("socket creation failed"); return -1; }
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY;
serverAddress.sin_port = htons(8080);
if (bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1){ perror("bind failed"); return -1; }
if (listen(serverSocket, 5) == -1){ perror("listen failed"); return -1; }
while(1){
clientAddressLength = sizeof(clientAddress);
int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressLength);
if (clientSocket == -1){ perror("accept failed"); continue; }
pthread_create(&thread, NULL, handleClient, (void*)&clientSocket);
}
#ifdef _WIN32
WSACleanup();
#endif
return 0;
}These examples demonstrate how Pthread can be leveraged on various operating systems to solve concurrency challenges, improve performance, and build scalable applications.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
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.