【问题标题】:OpenMP: Shared variables in single, nowait constructOpenMP:单个 nowait 构造中的共享变量
【发布时间】:2016-02-23 19:33:58
【问题描述】:

所以我有这个小sn-p代码:

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。 我在 a 上尝试过 #pragma omp flush 和 #pragma omp atomic 但没有用。我想在该块中使用 a 进行比较(即 if(a == 10)),因此一旦另一个线程进入单个块,更新值至关重要。我做错了什么?

【问题讨论】:

    标签: c multithreading openmp shared-memory


    【解决方案1】:

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

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

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

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

    【讨论】:

      【解决方案2】:

      我不认为你想要一个线程来执行你的代码块,而是你想要确保每个线程不会踩到afinished 中其他线程的数据。这是通过#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;
          }
      
      }
      

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

      【讨论】:

      • 这不是我的全部算法,这只是问题归结为的一个简单示例。我使用单个 nowait 构造来发送数据,一次只能执行一个线程。当一个线程在单个 nowait 构造中时,其他线程正在做其他工作。关键对我不起作用,因为其他线程将等待进入构造而不是工作。
      • 如果您将其用于工作分配,则应改为查看 OpenMP 任务,因为它们无需您编写所有这些不稳定的机制即可解决该问题。
      • 我想,只有任务有这么多依赖,我不能使用 OpenMP 4.0
      • @Circus,显式任务(无依赖关系)是 OpenMP 3.0 的一部分。不使用任务的唯一原因是如果您想保持与仅支持 OpenMP 2.0 的 MS Visual Studio 兼容。
      【解决方案3】:

      我真的不确定您在这里尝试实现什么...实际上,如果没有 single 块之外的其他线程完成任何类型的工作,我看不出结构的意义。

      无论如何,我尝试通过在块外添加printf() 语句来扩展您的示例,该语句还打印a 的值,以查看如何将其传输到其他线程。此外,由于您使用了single 指令,我假设您只希望一个线程执行该块,即使它位于while() 循环中。所以它看起来非常适合使用 OpenMP 锁...

      这是我想出的:

      #include <stdio.h>
      #include <omp.h>
      #include <unistd.h>
      
      int main() {
          int a = 10;
          bool finished = false;
          omp_lock_t lock;
          omp_init_lock( &lock );
          #pragma omp parallel num_threads( 3 ) shared( a, finished )
          {
              while( !finished ) {
                  if ( omp_test_lock( &lock ) ) {
                      printf( "[%d] a is: %d\n", omp_get_thread_num(), a );
                      #pragma omp atomic update
                      a--;
                      usleep( 10 );
                      finished = true;
                      #pragma omp flush( finished )
                      omp_unset_lock( &lock );
                  }
                  #pragma omp flush( finished, a )
                  printf( "[%d] outside of if block, a is: %d\n", omp_get_thread_num(), a );
              }
          }
          return 0;
      }
      

      我添加了对usleep() 的调用,以稍微延迟if 块内指令的执行,并让其他线程有机会打印一些东西。我已经用 gcc 4.9 和 5.3 版以及 Intel 编译器 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
      

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

      【讨论】:

      • 谢谢!我尝试了类似的事情并得到了类似的结果。我对它在我的示例中的行为方式更感兴趣。我认为一次只有一个线程能够进入单个构造,一旦另一个线程进入它,“a”应该已经更新了吗?我不明白为什么我需要在 single 里面加一个锁才能让它工作,因为无论如何一次只能有一个线程在里面
      猜你喜欢
      • 2012-04-14
      • 1970-01-01
      • 2016-06-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-31
      相关资源
      最近更新 更多