このセクションでは、データの競合の原因を診断する基本的な方法について説明します。
誤検知のデータの競合は、スレッドアナライザで報告されるが、実際には起こっていないデータの競合です。つまり、「誤検出」です。スレッドアナライザは、報告する誤検知の数を減らそうと試みます。ただし、ツールが正確なジョブを行えずに、誤検知のデータの競合を報告する場合があります。
誤検知のデータの競合は本当のデータの競合ではなく、したがってプログラムの動作に影響しないので、このデータの競合は無視できます。
誤検知のデータの競合の例については、誤検知を参照してください。レポートから誤検知のデータの競合を削除する方法については、スレッドアナライザユーザー APIを参照してください。
影響のないデータの競合は、存在していてもプログラムの正確さには影響しない意図的なデータの競合です。
一部のマルチスレッドアプリケーションでは、データの競合を引き起こすコードを意図的に使用します。設計によってデータの競合が存在するので、修正は必要ありません。ただし、場合によっては、このようなコードを正しく実行させるには非常に慎重を要します。これらのデータの競合については注意深く調べてください。
影響のない競合については、誤検知を参照してください。
スレッドアナライザは、プログラム内でデータの競合を見つけるときに役立ちますが、プログラム内のバグを自動的に見つけることも、見つかったデータの競合の修正方法を提示することもできません。データの競合は、バグによって生じることもあります。バグを見つけて修正することが重要です。単にデータの競合を取り除くだけでは正しいアプローチにはならず、以降のデバッグがさらに困難になる可能性があります。
このセクションでは、prime_omp.c でのバグを修正する方法について説明します。完全なファイルのリストについては、「prime_omp.c のソースコード」を参照してください。
配列 primes[ ] の要素でのデータの競合を削除するために、行 49 および 50 を critical セクションに移します。
46 #pragma omp parallel for 47 for (i = 2; i < N; i++) { 48 if ( is_prime(i) ) { #pragma omp critical { 49 primes[total] = i; 50 total++; } 51 } 52 }
また、次のように行 49 および 50 を 2 つの critical セクションに移すこともできますが、この変更ではプログラムを修正できません。
46 #pragma omp parallel fo 47 for (i = 2; i < N; i++) { 48 if ( is_prime (i) ) { #pragma omp critical { 49 primes [total] = i; } #pragma omp critical { 50 total++; } 51 } 52 }
スレッドは、排他的ロックを使用して primes[ ] 配列へのアクセスを制御しているので、行 49 および 50 の critical セクションによってデータの競合が取り除かれます。ただし、プログラムはまだ正しくありません。2 つのスレッドは、同じ total 値を使用して primes[ ] の同じ要素を更新する可能性があり、primes[ ] の要素の中には、値がまったく割り当てられないものが生じる可能性があります。
行 22 での pflag[ ] からの読み取りと、行 25 での pflag[ ] への書き込みとの 2 番目のデータの競合は間違った結果を招かないので、実際には影響のない競合です。影響のないデータの競合の修正は必須ではありません。
このセクションでは、prime_pthr.c でのバグを修正する方法について説明します。完全なファイルのリストについては、prime_pthr.c のソースコードを参照してください。
行 50 での prime[ ] のデータ競合と行 44 での total のデータ競合を取り除くには、これら 2 つの行の前後に相互排他ロック/ロック解除を追加することで、一度に prime[ ] や total を更新できるスレッドが 1 つだけになるようにします。
行 59 での i への書き込みと、行 39 での (*arg という) 同じメモリー位置の読み取りとのデータの競合では、別々のスレッドによる変数 i への共有アクセスに問題があることがわかります。prime_pthr.c の初期スレッドは、行 59-61 でループの子スレッドを作成し、関数 work() を実行するようにこれらをディスパッチします。ループ インデックス i は、アドレスで work() に渡されます。すべてのスレッドは i に対して同じメモリー位置にアクセスするので、各スレッドの i の値は一意のままではありませんが、初期スレッドがループ インデックスを増やすたびに変化します。別々のスレッドが同じ i の値を使用するので、データの競合が起こります。問題を修正する 1 つの方法は、i をアドレスではなく値で work() に渡すことです。
次のコードは、修正されたバージョンの prime_pthr.c です。
1 /* 2 * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All Rights Reserved. 3 */ 4 5 #include <stdio.h> 6 #include <math.h> 7 #include <pthread.h> 8 9 #define THREADS 4 10 #define N 10000 11 12 int primes[N]; 13 int pflag[N]; 14 int total = 0; 15 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 16 17 int is_prime(int v) 18 { 19 int i; 20 int bound = floor(sqrt(v)) + 1; 21 22 for (i = 2; i < bound; i++) { 23 /* no need to check against known composites */ 24 if (!pflag[i]) 25 continue; 26 if (v % i == 0) { 27 pflag[v] = 0; 28 return 0; 29 } 30 } 31 return (v > 1); 32 } 33 34 void * work(void *arg) 35 { 36 int start; 37 int end; 38 int i; 39 40 start = (N/THREADS) * ((int)arg) ; 41 end = start + N/THREADS; 42 for (i = start; i < end; i++) { 43 if ( is_prime(i) ) { 44 pthread_mutex_lock(&mutex); 45 primes[total] = i; 46 total++; 47 pthread_mutex_unlock(&mutex); 48 } 49 } 50 return NULL; 51 } 52 53 int main(int argn, char **argv) 54 { 55 int i; 56 pthread_t tids[THREADS-1]; 57 58 for (i = 0; i < N; i++) { 59 pflag[i] = 1; 60 } 61 62 for (i = 0; i < THREADS-1; i++) { 63 pthread_create(&tids[i], NULL, work, (void *)i); 64 } 65 66 i = THREADS-1; 67 work((void *)i); 68 69 for (i = 0; i < THREADS-1; i++) { 70 pthread_join(tids[i], NULL); 71 } 72 73 printf("Number of prime numbers between 2 and %d: %d\n", 74 N, total); 75 76 return 0; 77 }