什么是 OpenMP 中的“隐式同步”

Posted

技术标签:

【中文标题】什么是 OpenMP 中的“隐式同步”【英文标题】:What is "implicit synchronization" in OpenMP 【发布时间】:2019-08-27 17:47:52 【问题描述】:

,您如何发现其中之一?我的老师是这么说的

#pragma omp parallel
printf(“Hello 1\n”);

具有隐式同步。为什么?你怎么看?

【问题讨论】:

【参考方案1】:

同步是并行处理和 openmp 中的一个重要问题。通常并行处理是异步的。您知道有多个线程正在处理一个问题,但您无法确切知道它们的实际状态、它们正在进行的迭代等。同步允许您控制线程执行。

openmp 中有两种同步方式:显式和隐式。显式同步是使用允许创建 barrier 的特定 openmp 构造完成的:#pragma omp barrier。屏障是一种并行结构,只能由所有线程同时传递。因此,在屏障之后,您可以准确地知道所有线程的状态,更重要的是,它们完成了多少工作。

隐式同步在两种情况下完成:

在平行区域的末端。 Openmp 依赖于 fork-join 模型。当程序启动时,会创建一个线程(主线程)。当您通过#pragma omp parallel 创建并行部分时,会创建多个线程(fork)。这些线程将同时工作,并在并行部分结束时被销毁(join)。因此,在并行部分结束时,您会进行同步,并且您可以准确地知道所有线程的状态(它们已完成工作)。这就是您给出的示例中发生的情况。并行部分仅包含printf(),最后,程序等待所有线程终止后再继续。

在诸如#pragma omp for#pragma omp sections 之类的一些openmp 结构的末尾,有一个隐含的障碍。只要所有线程都没有到达屏障,就没有线程可以继续工作。准确了解不同线程所做的工作非常重要。

例如,考虑以下代码。

#pragma omp parallel

  #pragma omp for
  for(int i=0; i<N; i++)
    A[i]=f(i); // compute values for A
  #pragma omp for
  for(int j=0; j<N/2; j++)
    B[j]=A[j]+A[j+N/2];// use the previously computed vector A
 // end of parallel section

由于所有线程都是异步工作的,因此您不知道哪些线程已完成创建其向量A 的一部分。如果没有同步,线程可能会快速完成第一个for 循环的部分,进入第二个for 循环并访问向量A 的元素,而应该计算它们的线程仍在第一个循环并没有计算出A[i]的对应值。

这就是为什么 openmp 编译器添加一个隐式屏障来同步所有线程的原因。因此,您可以确定所有线程都已完成所有工作,并且当第二个 for 循环开始时,A 的所有值都已计算完毕。

但在某些情况下,不需要同步。例如,考虑以下代码:

#pragma omp parallel

  #pragma omp for
  for(int i=0; i<N; i++)
    A[i]=f(i); // compute values for A
  #pragma omp for
  for(int j=0; j<N/2; j++)
    B[j]=g(j);// compute values for B
 // end of parallel section

显然这两个循环是完全独立的,如果A 被正确计算以启动第二个for 循环并不重要。所以同步对程序的正确性没有任何帮助 添加同步屏障有两个主要缺点:

    如果函数f() 的运行时间相差很大,您可能有一些线程已完成工作,而其他线程仍在计算。同步将强制之前的线程等待,而这种空闲状态并不能正确利用并行性。

    同步成本很高。实现屏障的一种简单方法是在到达屏障时增加一个全局计数器,并等待计数器的值等于线程数omp_get_num_threads()。为了避免线程之间的竞争,全局计数器的递增必须使用需要大量周期的原子读取-修改-写入来完成,并且等待计数器的正确值通常使用浪费处理器的自旋锁来完成循环。

因此存在抑制隐式同步的构造,而对前一个循环进行编程的最佳方法是:

#pragma omp parallel

  #pragma omp for nowait  // nowait suppresses implicit synchronisations. 
  for(int i=0; i<N; i++)
    A[i]=f(i); // compute values for A
  #pragma omp for
  for(int j=0; j<N/2; j++)
    B[j]=g(j);// compute values for B
 // end of parallel section

这样,一旦线程在第一个循环中完成了它的工作,它就会立即开始处理第二个for 循环,并且根据实际程序,这可能会大大减少执行时间。

【讨论】:

以上是关于什么是 OpenMP 中的“隐式同步”的主要内容,如果未能解决你的问题,请参考以下文章

c++ openmp中的线程

GCC 中的 OpenMP 4.0:卸载到 nVidia GPU

如何修复 OpenMP 程序的 gdb 运行中的线程数

OpenMP 中的分析/调试 - Linux 和免费?

Java并发拾遗——并发JMM与重排序

如何确认多核系统中的openmp是不是使用了所有内核?