如何处理 OpenMP 中的数据竞争?
Posted
技术标签:
【中文标题】如何处理 OpenMP 中的数据竞争?【英文标题】:How do I deal with a data race in OpenMP? 【发布时间】:2014-11-18 15:42:51 【问题描述】:我正在尝试使用 OpenMP 将数字添加到数组中。以下是我的代码:
int* input = (int*) malloc (sizeof(int)*snum);
int sum = 0;
int i;
for(i=0;i<snum;i++)
input[i] = i+1;
#pragma omp parallel for schedule(static)
for(i=0;i<snum;i++)
int* tmpsum = input+i;
sum += *tmpsum;
这不会为sum
生成正确的结果。怎么了?
【问题讨论】:
sum
应该是一个 reduction 变量,reduction(+:sum)
【参考方案1】:
您的代码当前有一个race condition,这就是结果不正确的原因。为了说明原因,让我们用一个简单的例子:
您在 2 个线程上运行,数组为 int input[4] = 1, 2, 3, 4;
。您将 sum
正确初始化为 0
并准备好开始循环。在循环的第一次迭代中,线程 0 和线程 1 从内存中读取sum
为0
,然后将它们各自的元素添加到sum
,并将其写回内存。但是,这意味着线程 0 正在尝试将 sum = 1
写入内存(第一个元素是 1
和 sum = 0 + 1 = 1
),而线程 1 正在尝试将 sum = 2
写入内存(第二个元素是 @987654332 @ 和 sum = 0 + 2 = 2
)。此代码的最终结果取决于哪个线程最后完成,因此最后写入内存,这是一种竞争条件。不仅如此,在这种特殊情况下,代码可以产生的答案都不是正确的!有几种方法可以解决这个问题;我将在下面详细介绍三个基本的:
#pragma omp critical
:
在 OpenMP 中,有一个称为 critical
的指令。这限制了代码,因此一次只有一个线程可以做某事。例如,你的for
-loop 可以写成:
#pragma omp parallel for schedule(static)
for(i = 0; i < snum; i++)
int *tmpsum = input + i;
#pragma omp critical
sum += *tmpsum;
这消除了竞争条件,因为一次只有一个线程访问和写入sum
。但是,critical
指令对性能非常不利,并且可能会扼杀您从使用 OpenMP 获得的大部分(如果不是全部)收益。
#pragma omp atomic
:
atomic
指令与critical
指令非常相似。主要区别在于,虽然critical
指令适用于您希望一次执行一个线程的任何事情,但atomic
指令仅适用于内存读/写操作。由于我们在此代码示例中所做的只是读取和写入 sum,因此该指令将完美运行:
#pragma omp parallel for schedule(static)
for(i = 0; i < snum; i++)
int *tmpsum = input + i;
#pragma omp atomic
sum += *tmpsum;
atomic
的性能普遍明显优于critical
。但是,在您的特定情况下,它仍然不是最佳选择。
reduction
:
您应该使用的方法,以及其他人已经建议的方法,是reduction
。您可以通过将for
-loop 更改为:
#pragma omp parallel for schedule(static) reduction(+:sum)
for(i = 0; i < snum; i++)
int *tmpsum = input + i;
sum += *tmpsum;
reduction
命令告诉 OpenMP,当循环运行时,您希望每个线程跟踪自己的 sum
变量,并在循环结束时将它们全部加起来。这是最有效的方法,因为您的整个循环现在并行运行,唯一的开销是在循环结束时,每个线程的 sum
值需要相加。
【讨论】:
您的答案中有很多地方使用过process,而正确的词应该是threads。您可能需要更正此问题以防止混淆。 @HristoIliev:没错。我现在正在编写 MPI 代码,所以我整天都在考虑流程;因此混淆了。现已修复,谢谢。 感谢您的回答,如果 var-sum 是一个数组,例如“int sum[2]”。我使用了#pragma omp reduction(+:sum) 和#pragma omp reduction( +:sum[0]) #pragma omp reduction(+:sum[1]),不起作用!我该怎么办? @YOung:不要将sum
设为数组;事实上,保持循环之前的代码与之前完全相同。 NikolayKondratyev 在另一个答案中提供了一个工作代码示例。澄清一下:虽然我确实说过每个线程都会跟踪自己的 sum
变量,但编译器会使用 reduction
指令为您处理这些;您不需要自己设置阵列。事实上,如您所见,自己设置数组会混淆编译器,并导致代码产生不正确的结果。
@wolfPack88 使用“reduction”,我得到了正确的答案。但我尝试使用“critical”或“automic”来做到这一点。我发现“reduction”解决方案几乎是两倍快others;我如何使用 phtread 或 openmp 来获得这种性能【参考方案2】:
使用reduction
子句(description at MSDN)。
int* input = (int*) malloc (sizeof(int)*snum);
int sum = 0;
int i;
for(i=0;i<snum;i++)
input[i] = i+1;
#pragma omp parallel for schedule(static) reduction(+:sum)
for(i=0;i<snum;i++)
sum += input[i];
【讨论】:
以上是关于如何处理 OpenMP 中的数据竞争?的主要内容,如果未能解决你的问题,请参考以下文章
Android 中的两个并发 AsyncTasks - 如何处理竞争条件?
如何处理 SQLAlchemy、flask、python 中的唯一数据