次に pthr_prime.c のバグを修正する方法を示します。このファイルの全コードリストは、「2.1.2 pthr_prime.c の全コード」にあります。
pthr_prime.c 内の行 39 の total の read と行 40 の total への write とのデータ競合を解消するには、1 つの相互排他 (mutex) を使用します。この追加によって、pthr_prime.c 内のほかの 2 つのデータ競合、行 39 の prime[]
でのデータ競合と行 40 の total でのデータ競合も解決します。
行 55 の i への write と行 35 の i の read とのデータ競合、また行 22 の pflag[]
でのデータ競合は、異なるスレッドによる変数 i への共有アクセスの問題であることがわかります。pthr_prime.c 内の初期スレッドはループで子スレッドを作成して (ソース行 55 〜 57) 、その子スレッドをディスパッチして、関数 work() を操作します。ループのインデックスの i は、work() にアドレスで渡されます。すべてのスレッドが i について同じメモリー位置にアクセスするため、各スレッドに対する i の値が一意でなくなり、初期スレッドがループのインデックスをインクリメントするのに伴って値は変化します。異なるスレッドが同じ i 値を使用するため、データ競合が発生します。
この問題を解決する 1 つの方法は、i を work() に値渡しする方法です。この方法によって、すべてのスレッドが必ず一意の値を持つ専用の i のコピーを持つようになります。primes[]
に関する行 39 の write アクセスと行 65 の read アクセスとのデータ競合の解消では、前述の行 39 と 40 に使用した相互排他ロックと同じロックを使用して行 65 を保護できます。しかし、これは正しい解決方法ではありません。真の問題は、子スレッドが work() で total および primes[]
を更新している間に、メインスレッドが結果を報告する (行 50 〜 53) 可能性があることです。相互排他ロックを使用しても、スレッド間の適切な順序付けのための同期はとられません。1 つの正しい修正方法は、メインスレッドですべての子スレッドがジョインするまで待ってから結果を出力することです。
次に修正した pthr_prime.c を示します。
1 #include <stdio.h> 2 #include <math.h> 3 #include <pthread.h> 4 5 #define THREADS 4 6 #define N 3000 7 8 int primes[N]; 9 int pflag[N]; 10 int total = 0; 11 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 12 13 int is_prime(int v) 14 { 15 int i; 16 int bound = floor(sqrt(v)) + 1; 17 18 for (i = 2; i < bound; i++) { 19 /* 判定済み合成数のチェックは不要 */ 20 if (!pflag[i]) 21 continue; 22 if (v % i == 0) { 23 pflag[v] = 0; 24 return 0; 25 } 26 } 27 return (v > 1); 28 } 29 30 void *work(void *arg) 31 { 32 int start; 33 int end; 34 int i; 35 36 start = (N/THREADS) * ((int)arg) ; 37 end = start + N/THREADS; 38 for (i = start; i < end; i++) { 39 if ( is_prime(i) ) { 40 pthread_mutex_lock(&mutex); 41 primes[total] = i; 42 total++; 43 pthread_mutex_unlock(&mutex); 44 } 45 } 46 return NULL; 47 } 48 49 int main(int argn, char **argv) 50 { 51 int i; 52 pthread_t tids[THREADS-1]; 53 54 for (i = 0; i < N; i++) { 55 pflag[i] = 1; 56 } 57 58 for (i = 0; i < THREADS-1; i++) { 59 pthread_create(&tids[i], NULL, work, (void *)i); 60 } 61 62 i = THREADS-1; 63 work((void *)i); 64 65 for (i = 0; i < THREADS-1; i++) { 66 pthread_join(tids[i], NULL); 67 } 68 69 printf("Number of prime numbers between 2 and %d: %d\n", 70 N, total); 71 for (i = 0; i < total; i++) { 72 printf("%d\n", primes[i]); 73 } 74 }