POSIXスレッドで並行処理を書く。create/join の基本から、データ競合・mutex による排他制御まで。
#include <pthread.h> で使える。コンパイル時は -pthread を忘れずに。#include <stdio.h> #include <pthread.h> // スレッドが実行する関数: 引数も戻り値も 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); // 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; } // 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; // 全スレッドで共有 void *worker(void *arg) { for (int i = 0; i < LOOPS; i++) { counter++; // これが原子的ではない! } 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++ は見た目は1命令だが、CPUレベルでは「読む→加算→書く」の3ステップ。2スレッドがほぼ同時にやると、片方の更新が失われる。#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); // 常に4000000 pthread_mutex_destroy(&mtx); return 0; }
#include <stdatomic.h> atomic_long counter = 0; // worker内: atomic_fetch_add(&counter, 1); // ロックなしで安全
<stdatomic.h> は C11 で追加。単純なカウンタなら atomic の方が高速。複雑な不変条件を守るなら mutex。// NG: スレッドごとにロック順序が違う // Thread A: lock(m1) → lock(m2) // Thread B: lock(m2) → lock(m1) // → 両方が片方取った瞬間にデッドロック
pthread_mutex_trylock は取れなかったら即座に失敗を返す。失敗したら自分の持っているロックを解放して再試行。helgrind(valgrindツールの1つ)でデータ競合とデッドロックを検知できる。valgrind --tool=helgrind ./apppthread_cond_t(条件変数)を使って「空なら待つ」「満タンなら待つ」を実装する。clock_gettime で測って比較。スレッド数を 1, 2, 4, 8 と変えて傾向を観察。