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