POSIX Threads (Pthreads) is a standardized programming interface for thread creation and synchronization. This article provides an in-depth look at implementing and managing threads using the Pthreads API, with practical examples and best practices.
#include <pthread.h>
// Core threading functions
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
int pthread_join(pthread_t thread, void **retval);
int pthread_exit(void *retval);
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void* thread_function(void* arg) {
printf("Thread executing with argument: %d\n", *(int*)arg);
return NULL;
}
int main() {
pthread_t thread;
pthread_attr_t attr;
int arg = 42;
// Initialize attributes
pthread_attr_init(&attr);
// Set stack size (1MB)
pthread_attr_setstacksize(&attr, 1024 * 1024);
// Set detach state
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
// Create thread with attributes
if (pthread_create(&thread, &attr, thread_function, &arg) != 0) {
perror("Thread creation failed");
exit(1);
}
// Clean up attributes
pthread_attr_destroy(&attr);
// Wait for thread completion
pthread_join(thread, NULL);
return 0;
}
Detailed example of thread creation with various parameters:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
typedef struct {
int thread_id;
char* message;
int sleep_time;
} thread_params_t;
void* parameterized_thread(void* arg) {
thread_params_t* params = (thread_params_t*)arg;
printf("Thread %d starting with message: %s\n",
params->thread_id, params->message);
sleep(params->sleep_time);
printf("Thread %d finishing\n", params->thread_id);
pthread_exit(NULL);
}
int main() {
pthread_t threads[3];
thread_params_t params[3] = {
{1, "First Thread", 2},
{2, "Second Thread", 3},
{3, "Third Thread", 1}
};
for (int i = 0; i < 3; i++) {
if (pthread_create(&threads[i], NULL, parameterized_thread, ¶ms[i]) != 0) {
perror("Thread creation failed");
exit(1);
}
}
for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void* return_value_thread(void* arg) {
int* result = malloc(sizeof(int));
*result = 42;
return (void*)result;
}
int main() {
pthread_t thread;
void* result;
pthread_create(&thread, NULL, return_value_thread, NULL);
// Wait for thread and get return value
pthread_join(thread, &result);
printf("Thread returned: %d\n", *(int*)result);
free(result);
return 0;
}
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void* detached_thread(void* arg) {
printf("Detached thread running\n");
sleep(2);
printf("Detached thread finishing\n");
pthread_exit(NULL);
}
int main() {
pthread_t thread;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&thread, &attr, detached_thread, NULL);
pthread_attr_destroy(&attr);
printf("Main thread continuing...\n");
sleep(3); // Wait to see detached thread output
printf("Main thread exiting\n");
return 0;
}
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void* cancellable_thread(void* arg) {
int i = 0;
while (1) {
printf("Thread iteration %d\n", ++i);
sleep(1);
pthread_testcancel(); // Cancellation point
}
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, cancellable_thread, NULL);
sleep(3); // Let thread run for 3 seconds
printf("Cancelling thread...\n");
pthread_cancel(thread);
pthread_join(thread, NULL);
printf("Thread cancelled and joined\n");
return 0;
}
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int shared_resource = 0;
void* increment_with_mutex(void* arg) {
for (int i = 0; i < 1000000; i++) {
pthread_mutex_lock(&mutex);
shared_resource++;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, increment_with_mutex, NULL);
pthread_create(&thread2, NULL, increment_with_mutex, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("Final value: %d\n", shared_resource);
pthread_mutex_destroy(&mutex);
return 0;
}
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0;
void* producer(void* arg) {
pthread_mutex_lock(&mutex);
ready = 1;
printf("Producer: Data is ready\n");
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
return NULL;
}
void* consumer(void* arg) {
pthread_mutex_lock(&mutex);
while (!ready) {
printf("Consumer: Waiting for data\n");
pthread_cond_wait(&cond, &mutex);
}
printf("Consumer: Got data\n");
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t prod_thread, cons_thread;
pthread_create(&cons_thread, NULL, consumer, NULL);
sleep(1); // Ensure consumer starts first
pthread_create(&prod_thread, NULL, producer, NULL);
pthread_join(prod_thread, NULL);
pthread_join(cons_thread, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 3
pthread_barrier_t barrier;
void* barrier_thread(void* arg) {
int id = *(int*)arg;
printf("Thread %d before barrier\n", id);
pthread_barrier_wait(&barrier);
printf("Thread %d after barrier\n", id);
return NULL;
}
int main() {
pthread_t threads[NUM_THREADS];
int thread_ids[NUM_THREADS];
pthread_barrier_init(&barrier, NULL, NUM_THREADS);
for (int i = 0; i < NUM_THREADS; i++) {
thread_ids[i] = i;
pthread_create(&threads[i], NULL, barrier_thread, &thread_ids[i]);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
pthread_barrier_destroy(&barrier);
return 0;
}
#include <pthread.h>
#include <stdio.h>
pthread_key_t thread_key;
void cleanup_thread_data(void* data) {
free(data);
}
void* thread_function(void* arg) {
int* data = malloc(sizeof(int));
*data = *(int*)arg;
pthread_setspecific(thread_key, data);
// Access thread-local storage
int* tls_data = pthread_getspecific(thread_key);
printf("Thread %d: TLS value = %d\n", *((int*)arg), *tls_data);
return NULL;
}
int main() {
pthread_t threads[3];
int thread_args[3] = {1, 2, 3};
pthread_key_create(&thread_key, cleanup_thread_data);
for (int i = 0; i < 3; i++) {
pthread_create(&threads[i], NULL, thread_function, &thread_args[i]);
}
for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}
pthread_key_delete(thread_key);
return 0;
}
#include <pthread.h>
#include <stdio.h>
#include <sched.h>
void* priority_thread(void* arg) {
int policy;
struct sched_param param;
pthread_getschedparam(pthread_self(), &policy, ¶m);
printf("Thread priority: %d\n", param.sched_priority);
return NULL;
}
int main() {
pthread_t thread;
pthread_attr_t attr;
struct sched_param param;
pthread_attr_init(&attr);
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
param.sched_priority = 50;
pthread_attr_setschedparam(&attr, ¶m);
pthread_create(&thread, &attr, priority_thread, NULL);
pthread_join(thread, NULL);
pthread_attr_destroy(&attr);
return 0;
}
Here’s a complete example demonstrating multiple thread management concepts:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define NUM_THREADS 5
#define QUEUE_SIZE 10
typedef struct {
int data[QUEUE_SIZE];
int front;
int rear;
pthread_mutex_t mutex;
pthread_cond_t not_full;
pthread_cond_t not_empty;
} thread_safe_queue_t;
thread_safe_queue_t queue = {
.front = 0,
.rear = 0,
.mutex = PTHREAD_MUTEX_INITIALIZER,
.not_full = PTHREAD_COND_INITIALIZER,
.not_empty = PTHREAD_COND_INITIALIZER
};
void queue_init(thread_safe_queue_t* q) {
pthread_mutex_init(&q->mutex, NULL);
pthread_cond_init(&q->not_full, NULL);
pthread_cond_init(&q->not_empty, NULL);
}
void queue_destroy(thread_safe_queue_t* q) {
pthread_mutex_destroy(&q->mutex);
pthread_cond_destroy(&q->not_full);
pthread_cond_destroy(&q->not_empty);
}
void queue_push(thread_safe_queue_t* q, int value) {
pthread_mutex_lock(&q->mutex);
while ((q->rear + 1) % QUEUE_SIZE == q->front) {
pthread_cond_wait(&q->not_full, &q->mutex);
}
q->data[q->rear] = value;
q->rear = (q->rear + 1) % QUEUE_SIZE;
pthread_cond_signal(&q->not_empty);
pthread_mutex_unlock(&q->mutex);
}
int queue_pop(thread_safe_queue_t* q) {
pthread_mutex_lock(&q->mutex);
while (q->front == q->rear) {
pthread_cond_wait(&q->not_empty, &q->mutex);
}
int value = q->data[q->front];
q->front = (q->front + 1) % QUEUE_SIZE;
pthread_cond_signal(&q->not_full);
pthread_mutex_unlock(&q->mutex);
return value;
}
void* producer(void* arg) {
int id = *(int*)arg;
for (int i = 0; i < 5; i++) {
int value = id * 100 + i;
queue_push(&queue, value);
printf("Producer %d: Pushed %d\n", id, value);
sleep(1);
}
return NULL;
}
void* consumer(void* arg) {
int id = *(int*)arg;
for (int i = 0; i < 5; i++) {
int value = queue_pop(&queue);
printf("Consumer %d: Popped %d\n", id, value);
sleep(1);
}
return NULL;
}
int main() {
pthread_t producers[NUM_THREADS];
pthread_t consumers[NUM_THREADS];
int thread_ids[NUM_THREADS];
queue_init(&queue);
for (int i = 0; i < NUM_THREADS; i++) {
thread_ids[i] = i;
pthread_create(&producers[i], NULL, producer, &thread_ids[i]);
pthread_create(&consumers[i],
NULL, consumer, &thread_ids[i]);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(producers[i], NULL);
pthread_join(consumers[i], NULL);
}
queue_destroy(&queue);
printf("All threads have finished execution\n");
return 0;
}
queue_push
adds an element to the queue, and queue_pop
removes an element.pthread_join
ensures that the main thread waits for all threads to complete.pthread_cancel
) unless absolutely necessary.pthread_rwlock
for scenarios where multiple threads read data but only a few write.Thread creation and management are essential skills for building efficient, concurrent applications. POSIX Threads (Pthreads) provide a robust API for creating, synchronizing, and managing threads. By understanding thread attributes, synchronization primitives, and best practices, developers can write scalable and performant multithreaded programs. However, care must be taken to avoid common pitfalls like race conditions and deadlocks.