この節では、データ競合の原因を究明する基本的な方法を説明します。
誤検出データ競合とは、スレッドアナライザによって報告はされますが、実際には発生しなかったデータ競合です。スレッドアナライザは、報告する誤検出の数を減らそうと試みます。ただし、正確なジョブを実行できず、誤検出データ競合を報告することがあります。
誤検出データ競合は真のデータ競合ではなく、プログラムの動作に影響しないため、無視することができます。
誤検出データ競合例については、「2.5 誤検出 (False Positive)」を参照してください。レポートでの誤検出データ競合の解消方法については、「A.1 スレッドアナライザのユーザー API」を参照してください。
良性のデータ競合は、その存在がプログラムの正確さに影響することのない意図的なデータ競合です。
マルチスレッドアプリケーションの中には、データ競合を起こす可能性があるコードが意図的に使用されているものがあります。そうしたデータ競合は意図的に存在しているため、修正する必要はありません。しかし、場合によっては、そうしたコードを正しく実行するのは難しく細心の注意が必要です。慎重に調査してください。
良性のデータ競合の詳細は、「2.5 誤検出 (False Positive)」を参照してください。
スレッドアナライザはプログラム内のデータ競合の発見に役立てられますが、プログラム内のバグを自動的に発見することも、発見されたデータ競合を解消する方法の提案することもできません。データ競合がバグによって引き起こされている可能性があります。大切なのは、バグを見つけて修正することです。単にデータ競合を排除することは適切なアプローチではなく、以後のデバッグをさらに困難にすることもあります。データ競合ではなくバグを修正してください。
次に omp_prime.c のバグを修正する方法を示します。このファイルの全コードリストは、「2.1.1 omp_prime.c の全コード」にあります。
行 45 の total の read と行 46 の total への write とのデータ競合を解決するには、行 45 と 46 をクリティカルセクションに移動します。クリティカルセクションはこの 2 つの行を保護し、データ競合の発生を防ぎます。次に修正したコードを示します。
42 #pragma omp parallel for . 43 for (i = 2; i < N; i++) { 44 if ( is_prime(i) ) { #pragma omp critical { 45 primes[total] = i; 46 total++; } 47 } 48 }
1 つのクリティカルセクションを追加することで、omp_prime.c 内のほかの 2 つのデータ競合も解決されます。行 45 の prime[]
でのデータ競合だけでなく、行 46 の total でのデータ競合も解決します。4 つ目の、 行 18 の pflag[]
の read と行 21 の pflag[]
への write とのデータ競合は、不正な結果につながることはないため、実際には良性のデータ競合です。良性のデータ競合の解決は重要ではありません。
次に示すように行 45 と 46 をクリティカルセクションに移動することもできますが、この変更はプログラムの問題の解決になりません。
42 #pragma omp parallel for . 43 for (i = 2; i < N; i++) { 44 if ( is_prime(i) ) { #pragma omp critical { 45 primes[total] = i; } #pragma omp critical { 46 total++; } 47 } 48 }
スレッドが排他的ロックを使用して total へのアクセスを制御していないため、行 45 と 46 を含むこのクリティカルセクションによってデータ競合は解消されます。行 46 を含むクリティカルセクションは、total の演算値が必ず正しくなるようにします。しかし、依然としてプログラムは正しくありません。2 つのスレッドが、同じ total 値を使用して primes[]
の同じ要素を更新する可能性があります。さらに、primes[]
内の一部要素にまったく値が代入されない可能性があります。
次に 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 }