POSIX Concurrency in C - Complete Guide2
POSIX Concurrency in C - Complete Guide2
Table of Contents
1. Multithreaded Programming Basics
2. Basic Thread Creation Example
3. Thread Synchronization
4. Debugging Multithreaded Applications
Thread vs Process
Aspect Process Thread
Thread Attributes
Detached vs Joinable: Joinable threads must be joined; detached threads clean up automatically
Stack Size: Can be configured (default varies by system)
Shared Resources
Threads within a process share:
Global variables
Heap memory
File descriptors
Signal handlers
Process ID
Program counter
Thread Standards
POSIX Threads (pthreads): IEEE POSIX 1003.1c standard
// Thread function
void* worker_thread(void* arg) {
thread_data_t* data = (thread_data_t*)arg;
// Simulate work
sleep(2);
int main() {
const int NUM_THREADS = 3;
pthread_t threads[NUM_THREADS];
thread_data_t* thread_data[NUM_THREADS];
int rc;
// Create threads
for (int i = 0; i < NUM_THREADS; i++) {
// Allocate thread data on heap (not stack)
thread_data[i] = malloc(sizeof(thread_data_t));
if (!thread_data[i]) {
fprintf(stderr, "Failed to allocate memory for thread data\n");
exit(1);
}
thread_data[i]->thread_id = i;
snprintf(thread_data[i]->message, sizeof(thread_data[i]->message),
"Hello from thread %d", i);
A critical section is a code segment that accesses shared resources and must be executed atomically.
Synchronization Mechanisms
typedef struct {
int thread_id;
int iterations;
} thread_param_t;
pthread_exit(NULL);
}
int main() {
const int NUM_THREADS = 5;
const int ITERATIONS_PER_THREAD = 1000;
pthread_t threads[NUM_THREADS];
thread_param_t* params[NUM_THREADS];
int rc;
// Create threads
for (int i = 0; i < NUM_THREADS; i++) {
params[i] = malloc(sizeof(thread_param_t));
if (!params[i]) {
fprintf(stderr, "Memory allocation failed\n");
exit(1);
}
params[i]->thread_id = i;
params[i]->iterations = ITERATIONS_PER_THREAD;
// Cleanup mutex
pthread_mutex_destroy(&counter_mutex);
return 0;
}
Semaphores
c
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 5
#define NUM_PRODUCERS 2
#define NUM_CONSUMERS 2
#define ITEMS_TO_PRODUCE 10
// Shared buffer
int buffer[BUFFER_SIZE];
int buffer_index = 0;
typedef struct {
int id;
int items_to_process;
} worker_param_t;
// Lock buffer
if (pthread_mutex_lock(&buffer_mutex) != 0) {
perror("pthread_mutex_lock");
pthread_exit(NULL);
}
// Unlock buffer
if (pthread_mutex_unlock(&buffer_mutex) != 0) {
perror("pthread_mutex_unlock");
pthread_exit(NULL);
}
pthread_exit(NULL);
}
// Lock buffer
if (pthread_mutex_lock(&buffer_mutex) != 0) {
perror("pthread_mutex_lock");
pthread_exit(NULL);
}
// Unlock buffer
if (pthread_mutex_unlock(&buffer_mutex) != 0) {
perror("pthread_mutex_unlock");
pthread_exit(NULL);
}
pthread_exit(NULL);
}
int main() {
pthread_t producers[NUM_PRODUCERS];
pthread_t consumers[NUM_CONSUMERS];
worker_param_t* producer_params[NUM_PRODUCERS];
worker_param_t* consumer_params[NUM_CONSUMERS];
// Initialize semaphores
if (sem_init(&empty_slots, 0, BUFFER_SIZE) != 0) {
perror("sem_init(empty_slots)");
exit(1);
}
if (sem_init(&full_slots, 0, 0) != 0) {
perror("sem_init(full_slots)");
exit(1);
}
// Create producers
for (int i = 0; i < NUM_PRODUCERS; i++) {
producer_params[i] = malloc(sizeof(worker_param_t));
producer_params[i]->id = i;
producer_params[i]->items_to_process = ITEMS_TO_PRODUCE;
// Create consumers
for (int i = 0; i < NUM_CONSUMERS; i++) {
consumer_params[i] = malloc(sizeof(worker_param_t));
consumer_params[i]->id = i;
consumer_params[i]->items_to_process = ITEMS_TO_PRODUCE;
// Cleanup
sem_destroy(&empty_slots);
sem_destroy(&full_slots);
pthread_mutex_destroy(&buffer_mutex);
bash
bash
bash
bash
// Critical section
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
return NULL;
}
// Critical section
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);
return NULL;
}
// Critical section
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
return NULL;
}
Error Handling
c
rc = pthread_join(thread, NULL);
if (rc) {
fprintf(stderr, "pthread_join failed: %s\n", strerror(rc));
}
Memory Management
c
int main() {
pthread_t thread;
thread_param_t* param = malloc(sizeof(thread_param_t));
param->data = 100;
void* result;
pthread_join(thread, &result);
pthread_mutex_unlock(&mutex);
shared_variable++;
expensive_computation(); // Don't do this inside lock
file_operations(); // Don't do this inside lock
pthread_mutex_unlock(&mutex);
}
// Critical section
shared_variable++;
rc = pthread_mutex_unlock(&mutex);
if (rc) {
fprintf(stderr, "Mutex unlock failed: %s\n", strerror(rc));
return -1;
}
return 0;
}
Thread Lifecycle Management
void proper_thread_management() {
const int NUM_THREADS = 5;
pthread_t threads[NUM_THREADS];
// Cleanup resources
pthread_mutex_destroy(&mutex);
}
#define QUEUE_SIZE 10
#define NUM_ITEMS 50
typedef struct {
int* items;
int front;
int rear;
int count;
int size;
pthread_mutex_t mutex;
pthread_cond_t not_full;
pthread_cond_t not_empty;
} queue_t;
typedef struct {
int id;
queue_t* queue;
int items_to_process;
} worker_t;
// Initialize queue
int queue_init(queue_t* q, int size) {
q->items = malloc(size * sizeof(int));
if (!q->items) {
return -1;
}
q->front = 0;
q->rear = 0;
q->count = 0;
q->size = size;
if (pthread_mutex_init(&q->mutex, NULL) != 0) {
free(q->items);
return -1;
}
if (pthread_cond_init(&q->not_full, NULL) != 0) {
pthread_mutex_destroy(&q->mutex);
free(q->items);
return -1;
}
if (pthread_cond_init(&q->not_empty, NULL) != 0) {
pthread_cond_destroy(&q->not_full);
pthread_mutex_destroy(&q->mutex);
free(q->items);
return -1;
}
return 0;
}
// Destroy queue
void queue_destroy(queue_t* q) {
pthread_cond_destroy(&q->not_empty);
pthread_cond_destroy(&q->not_full);
pthread_mutex_destroy(&q->mutex);
free(q->items);
}
// Add item
q->items[q->rear] = item;
q->rear = (q->rear + 1) % q->size;
q->count++;
if (pthread_mutex_unlock(&q->mutex) != 0) {
return -1;
}
return 0;
}
// Get item
*item = q->items[q->front];
q->front = (q->front + 1) % q->size;
q->count--;
if (pthread_mutex_unlock(&q->mutex) != 0) {
return -1;
}
return 0;
}
if (queue_put(worker->queue, item) != 0) {
fprintf(stderr, "Producer %d: Failed to put item %d\n",
worker->id, item);
pthread_exit(NULL);
}
if (queue_get(worker->queue, &item) != 0) {
fprintf(stderr, "Consumer %d: Failed to get item\n", worker->id);
pthread_exit(NULL);
}
int main() {
queue_t queue;
const int NUM_PRODUCERS = 2;
const int NUM_CONSUMERS = 2;
pthread_t producers[NUM_PRODUCERS];
pthread_t consumers[NUM_CONSUMERS];
worker_t* producer_workers[NUM_PRODUCERS];
worker_t* consumer_workers[NUM_CONSUMERS];
// Initialize queue
if (queue_init(&queue, QUEUE_SIZE) != 0) {
fprintf(stderr, "Failed to initialize queue\n");
exit(1);
}
producer_workers[i]->id = i;
producer_workers[i]->queue = &queue;
producer_workers[i]->items_to_process = NUM_ITEMS / NUM_PRODUCERS;
consumer_workers[i]->id = i;
consumer_workers[i]->queue = &queue;
consumer_workers[i]->items_to_process = NUM_ITEMS / NUM_CONSUMERS;
// Cleanup
queue_destroy(&queue);
Compilation Commands
bash
# Basic compilation
gcc -pthread -o program program.c
Key Takeaways
1. Always handle errors after pthread function calls