POSIX threads for concurrent programming: create / join, data races, mutex, deadlock.
<pthread.h> and compile with -pthread.#include <stdio.h> #include <pthread.h> // Thread entry point: both arg and return are void* void *worker(void *arg) { int id = *(int *)arg; printf("hello from thread %d\n", id); return NULL; } int main(void) { pthread_t t1, t2; int a = 1, b = 2; pthread_create(&t1, NULL, worker, &a); pthread_create(&t2, NULL, worker, &b); pthread_join(t1, NULL); // wait for t1 pthread_join(t2, NULL); printf("main done\n"); return 0; }
void *compute(void *arg) { int x = *(int *)arg; int *result = malloc(sizeof(int)); *result = x * x; return result; } // in main: void *ret; pthread_join(t, &ret); printf("result = %d\n", *(int *)ret); free(ret);
#include <stdio.h> #include <pthread.h> #define N 4 #define LOOPS 1000000 long counter = 0; // shared void *worker(void *arg) { for (int i = 0; i < LOOPS; i++) { counter++; // NOT atomic! } return NULL; } int main(void) { pthread_t th[N]; for (int i = 0; i < N; i++) pthread_create(&th[i], NULL, worker, NULL); for (int i = 0; i < N; i++) pthread_join(th[i], NULL); printf("expected: %d\n", N * LOOPS); printf("actual: %ld\n", counter); return 0; }
counter++ looks atomic but is really "load, add, store" at the CPU level. Two threads doing it concurrently lose updates.#include <stdio.h> #include <pthread.h> #define N 4 #define LOOPS 1000000 long counter = 0; pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; void *worker(void *arg) { for (int i = 0; i < LOOPS; i++) { pthread_mutex_lock(&mtx); counter++; pthread_mutex_unlock(&mtx); } return NULL; } int main(void) { pthread_t th[N]; for (int i = 0; i < N; i++) pthread_create(&th[i], NULL, worker, NULL); for (int i = 0; i < N; i++) pthread_join(th[i], NULL); printf("counter = %ld\n", counter); // always 4000000 pthread_mutex_destroy(&mtx); return 0; }
#include <stdatomic.h> atomic_long counter = 0; // in worker: atomic_fetch_add(&counter, 1); // safe, lock-free
<stdatomic.h> is C11. Use atomics for simple counters; use mutex to protect complex invariants.// BAD: different lock orders per thread // Thread A: lock(m1) β lock(m2) // Thread B: lock(m2) β lock(m1) // β both hold one and wait for the other
pthread_mutex_trylock fails instead of waiting. If you can't grab the second lock, release the first and retry.valgrind --tool=helgrind ./app detects data races and potential deadlocks.pthread_cond_t to wait when empty/full.clock_gettime for 1, 2, 4, 8 threads and plot.