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; }
main can exit before the threads finish and you'll leak resources. (Unless you deliberately detach the thread.)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 at the CPU level it's really three steps β load, add, store. Two threads doing it concurrently will 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, and mutexes for protecting more 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 the buffer is empty or full.clock_gettime for 1, 2, 4, and 8 threads, then plot the results.Check your understanding of this lesson!
pthread_create(&tid, NULL, func, arg) launches a new thread that starts executing at func.
Code wrapped in pthread_mutex_lock / unlock can only be executed by one thread at a time.
pthread_join(tid, &retval) blocks until that thread finishes. Skip it, and its resources won't be reclaimed.