此部分提供诊断数据争用原因的基本策略。
误报数据争用是线程分析器报告了实际上未发生的数据争用。线程分析器尝试减少误报数。但是,存在该工具无法执行准确的作业并可能误报数据争用的情况。
可以忽略误报的数据争用,因为它不是真正的数据争用,因此不会影响程序的行为。
有关误报数据争用的一些示例,请参见 2.5 误报。有关如何避免误报数据争用的信息,请参见A.1 线程分析器的用户 API。
良性数据争用是指其存在不会影响程序正确性的有意数据争用。
有些多线程应用程序会有意使用可能导致数据争用的代码。由于那里的数据争用是设计使然,因此无需进行修复。但是,在某些情况下,使这样的代码正确运行是相当棘手的。应仔细检查这些数据争用。
有关良性争用的更多详细信息,请参见 2.5 误报。
线程分析器可以帮助查找程序中的数据争用,但是它无法自动查找程序中的错误,也无法建议如何修复所找到的数据争用。数据争用也可能是由错误引入的。找到并修复错误是很重要的。仅仅消除数据争用并不是正确的方法,这样做可能会使进一步调试变得更加困难。修复错误而不是修复数据争用。
以下说明如何修复 omp_prime.c 中的错误。有关完整的文件列表,请参见 2.1.1 omp_prime.c 的完整列表。
将第 45 行和第 46 行移动到临界段中,以便消除第 45 行上 total 的读取和第 46 行上 total 的写入之间的数据争用。临界段保护这两行并防止数据争用。以下是更正后的代码:
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 }
请注意,添加单个临界段还修复了 omp_prime.c 中的两个其他数据争用。它修复了第 45 行上 prime[]
的数据争用,以及第 46 行上 total 的数据争用。第 18 行上 pflag[]
的读取和第 21 行上 pflag[]
的写入之间的第四个数据争用实际上是良性争用,因为它不会导致不正确的结果。修复良性数据争用不是必需的。
还可以将第 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 }
第 45 行和第 46 行周围的临界段消除了数据争用,因为线程不使用任何互斥锁控制其对 total 的访问。第 46 行周围的临界段确保 total 的计算值是正确的。但是,程序仍是不正确的。两个线程可能使用 total 的同一值更新 primes[]
的同一元素。此外,primes[]
中的某些元素可能根本未赋值。
以下说明如何修复 pthr_prime.c 中的错误。有关完整的文件列表,请参见 2.1.2 pthr_prime.c 的完整列表。
使用单个互斥锁消除 pthr_prime.c 中第 39 行上 total 的读取和第 40 行上 total 的写入之间的数据争用。此添加还修复了 pthr_prime.c 中的两个其他数据争用:第 39 行上 prime[]
的数据争用以及第 40 行上 total 的数据争用。
第 55 行上 i 的写入和第 35 行上 i 的读取之间的数据争用以及第 22 行上 pflag[]
的数据争用显示了不同线程对变量 i 进行共享访问的问题。pthr_prime.c 中的初始线程在循环中创建子线程(源代码的第 55-57 行),并调度它们处理函数 work()。循环索引 i 按地址传递到 work()。由于所有线程都访问 i 的同一内存位置,因此每个线程的 i 值不会保持唯一,而是将随初始线程递增循环索引而改变。由于不同线程使用 i 的同一值,因此发生了数据争用。
修复该问题的一种方法是将 i 按值传递到 work()。这确保每个线程都具有自己的带有唯一值的专用 i 副本。要消除第 39 行 上写入访问和第 65 行上读取访问之间的 primes[]
数据争用,可以使用与前面第 39 行和第 40 行所用相同的互斥锁保护第 65 行。但是,这不是正确的修复方法。真正的问题是,主线程可能会在子线程仍在函数 work() 中更新 total 和 primes[]
的同时报告结果(第 50 行到第 53 行)。使用互斥锁并不提供线程之间的正确排序同步。一种正确的修复方法是在打印出结果之前让主线程等待所有子线程加入。
以下是更正后的 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 /* no need to check against known composites */ 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 }