このセクションでは、データの競合の原因を診断する基本的な方法について説明します。
誤検知のデータの競合は、スレッドアナライザで報告されるが、実際には起こっていないデータの競合です。つまり、「誤検出」です。スレッドアナライザは、報告する誤検知の数を減らそうと試みます。ただし、ツールが正確なジョブを行えずに、誤検知のデータの競合を報告する場合があります。
誤検知のデータの競合は本当のデータの競合ではなく、したがってプログラムの動作に影響しないので、このデータの競合は無視できます。
誤検知のデータの競合の例については、誤検知を参照してください。レポートから誤検知のデータの競合を削除する方法については、スレッドアナライザユーザー 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 }