2.1.1 データの競合チュートリアルのソースファイルの入手
2.1.3.1 prime_omp.c および prime_pthr.c でのデータの競合の影響
2.2.3.1 スレッドアナライザを使用したデータの競合実験の表示
この節では、データの競合の原因を診断する基本的な方法について説明します。
誤検知のデータの競合は、スレッドアナライザで報告されますが、実際には起こっていないデータの競合です。スレッドアナライザは、報告する誤検知の数を減らそうと試みます。ただし、ツールが正確なジョブを行えずに、誤検知のデータの競合を報告する場合があります。
誤検知のデータの競合は本当のデータの競合ではなく、したがってプログラムの動作に影響しないので、このデータの競合は無視できます。
誤検知のデータの競合の例については、「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 }