C OpenMP:单个nowait构造中的共享变量

C OpenMP:单个nowait构造中的共享变量,c,multithreading,openmp,shared-memory,C,Multithreading,Openmp,Shared Memory,我有一段代码: int a = 10; bool finished = false; #pragma omp parallel num_threads(3) shared(a, finished) { while(!finished) { #pragma omp single nowait { printf("[%d] a is: %d\n", omp_get_thread_num(), a); a--;

我有一段代码:

int a = 10;
bool finished = false;

#pragma omp parallel num_threads(3) shared(a, finished)
{
    while(!finished) {

        #pragma omp single nowait
        {
            printf("[%d] a is: %d\n", omp_get_thread_num(), a);
            a--;
            finished = true;
        }

    }
}
输出是

[0] a is: 10
[2] a is: 10
[1] a is: 10
这根本不是我所期望的。我意识到,在退出while循环之前,所有线程可能都会进入单个构造,但为什么它们都说相同的a?进入的第二个线程的a=9,第三个线程的a=8。
我试过在一台计算机上使用#pragma omp flush和#pragma omp atomic,但没有用。我希望在该块中使用a进行比较(即,如果(a==10)),因此在另一个线程进入单个块时更新该值是至关重要的。我做错了什么?

我认为您不希望单个线程执行代码块,而是希望确保每个线程都不会在
a
中的其他线程的数据上跺脚。这是通过
#pragma omp critical
实现的。此外,您不能有
nowait
子句,因为根据定义,在另一个线程完成关键区域之前,其他线程不会进入代码块

while(!finished) {

    // only 1 thread will enter this region at a time
    #pragma omp critical
    {
        printf("[%d] a is: %d\n", omp_get_thread_num(), a);
        a--;
        finished = true;
    }

}

另外,请注意,具有这种线程相互依赖性可能会降低性能。我建议您避免这种情况,并更改您的算法,除非您认为没有其他方法可以做到这一点。

我真的不知道您试图在这里实现什么。。。实际上,如果没有
单个
块之外的其他线程所做的任何工作,我看不出结构的意义

无论如何,我尝试了一点外推,并扩展了您的示例,在块外添加了一个
printf()
语句,该语句也会打印
a
的值,以查看这是如何传输到其他线程的。此外,由于您使用了
单个
指令,因此我假设您只需要一个线程执行该块,即使它位于
while()
循环上。所以它看起来非常适合OpenMP锁的使用

以下是我的想法:

#包括
#包括
#包括
int main(){
INTA=10;
bool finished=false;
omp\u锁\u t锁;
omp_init_lock(&lock);
#pragma omp并行线程数(3)共享(a,完成)
{
当(!完成){
if(omp_测试_锁定(&L)){
printf(“[%d]a是:%d\n”,omp\u get\u thread\u num(),a);
#pragma omp原子更新
a——;
usleep(10);
完成=正确;
#pragma omp冲洗(已完成)
omp_解除锁定(&lock);
}
#pragma omp冲洗(已完成,a)
printf(“[%d]在if块之外,a是:%d\n”,omp\u get\u thread\u num(),a);
}
}
返回0;
}
我添加了对
usleep()
的调用,以稍微延迟
if
块中指令的执行,并给其他线程打印内容的机会。我已经用gcc版本4.9和5.3以及Intel compiler 16.0对它进行了测试,所有3个版本都提供了相同的输出类型(在运行之间的打印顺序和数量上显然有一些变化)

结果如下所示:

~/tmp$ icpc -fopenmp omplock.cc
~/tmp$ ./a.out 
[0] a is: 10
[1] outside of if block, a is: 10
[1] outside of if block, a is: 9
[1] outside of if block, a is: 9
[1] outside of if block, a is: 9
[1] outside of if block, a is: 9
[1] outside of if block, a is: 9
[2] outside of if block, a is: 10
[1] outside of if block, a is: 9
[0] outside of if block, a is: 9

这种方法能满足您的需求吗?

您的代码的问题是
单一指令基本上没有效果。通过指定
nowait
子句,其他线程将不会在块结束时等待,而是立即进入下一个循环迭代

这意味着,到达
单个
构造的第一个线程将在第一次迭代中执行块。其余的线程将跳过该块,并在第二次迭代中立即再次到达
单个
构造。剩下的两个线程中的一个也会进入块(因为这是一个新的迭代,因此是块的另一次出现)。另一个在第三次迭代中再次跳过并立即进入。最后,所有线程几乎同时执行并打印
a
的初始值,因为在此时间点之前,没有线程成功地递减
a

如果按如下方式交换
单个
块中的语句,您将看到
a
发生变化。但是,这些值及其顺序将不确定

#pragma omp single nowait
{
    a--;
    printf("[%d] a is: %d\n", omp_get_thread_num(), a);
    finished = true;
}

这不是我的全部算法,这只是一个简单的例子,说明问题的根源。我使用单个nowait构造来发送数据,一次只有一个线程可以发送数据。当一个线程处于单个nowait构造中时,其他线程正在执行其他工作。Critical对我来说不起作用,因为其他线程将等待进入构造而不是执行工作。如果您将其用于工作分配,则应查看OpenMP任务,因为它们解决了该问题,而无需编写所有这些古怪的机制。我想,只有任务有这么多依赖项,我不能使用OpenMP 4。0@Circus,显式任务(无依赖项)是OpenMP 3.0的一部分。不使用任务的唯一原因是您希望与仅支持OpenMP 2.0的MS Visual Studio保持兼容。谢谢!我尝试过类似的事情,得到了相似的结果。我更感兴趣的是它为什么会像我的示例中那样运行。我认为一次只有一个线程可以进入单个构造,而一旦另一个线程进入它,“a”应该已经更新了吗?我不明白为什么我需要一个锁在单一的工作,因为一次只能有一个线程在那里无论如何