omp critical和omp single之间的区别

Ami*_*mir 31 parallel-processing openmp

我想了解的确切差异#pragma omp critical#pragma omp singleOpenMP中:

微软的定义是:

  • 单一:允许您指定应在单个线程上执行一段代码,而不一定是主线程.
  • 严重:指定代码一次只能在一个线程上执行.

所以这意味着在两者中,之后的代码的确切部分将仅由一个线程执行而其他线程将不会进入该部分,例如,如果我们打印某些内容,我们将在屏幕上看到一次结果,对吧?

差异怎么样?它看起来很重要,可以处理执行时间,但不是单一的!但是我没有看到练习中的任何差异!这是否意味着其他线程(不进入该部分)的某种等待或同步被认为是关键的,但没有什么能够将其他线程保持在单一状态?它如何改变实践中的结果?

我很感激,如果有人能够通过一个例子向我澄清这一点.谢谢!

Gil*_*les 65

single并且critical是两个非常不同的东西.如你所说:

  • single指定一段代码应该由单个线程执行(不一定是主线程)
  • critical指定代码一次由一个线程执行

所以前者执行一次,而后者执行的次数与线程数一样多.

例如以下代码

int a=0, b=0;
#pragma omp parallel num_threads(4)
{
    #pragma omp single
    a++;
    #pragma omp critical
    b++;
}
printf("single: %d -- critical: %d\n", a, b);
Run Code Online (Sandbox Code Playgroud)

将打印

single: 1 -- critical: 4
Run Code Online (Sandbox Code Playgroud)

我希望你现在能看到更好的差异.

为了完整起见,我可以补充一点:

  • mastersingle与两个差异非常相似:
    1. master只有在single可以通过首先到达该区域的任何线程执行时,才由主机执行; 和
    2. single在完成区域时有一个隐含的障碍,所有线程都在等待同步,而master没有任何线程.
  • atomic非常相似critical,但仅限于选择简单的操作.

我添加了这些精确度,因为这两对指令往往是人们倾向于混淆的......


Hri*_*iev 28

single并且critical属于两个完全不同的OpenMP构造类.single是一个工作共享的结构,旁边forsections.工作共享构造用于在线程之间分配一定量的工作.这样的构造在某种意义上是"集体的",即在正确的OpenMP程序中,所有线程在执行时必须遇到它们,并且还以相同的顺序顺序,也包括barrier构造.三个工作共享结构涵盖三种不同的一般情况:

  • for (aka loop construct)自动分配线程中循环的迭代 - 在大多数情况下,所有线程都可以完成工作;
  • sections在线程中分配一系列独立的代码块 - 一些线程可以完成工作.这是for构造的概括,作为具有100次迭代的循环可以表示为例如10个循环的循环,每个循环具有10次迭代.
  • single单挑一个代码块只能由一个线程执行,通常是遇到它的第一个(实现细节) - 只有一个线程可以工作.single在很大程度上只相当于sections一个部分.

所有工作分享结构的一个共同特点是在其端部,该势垒的隐式屏障的存在可能通过将被关闭nowait子句为相应的OpenMP构造,但标准不要求这样的行为,并与一些OpenMP的运行时将阻挡可能尽管存在,仍继续在那里nowait.错误地排序(即在某些线程中不按顺序)工作共享结构可能因此导致死锁.当障碍存在时,正确的OpenMP程序永远不会死锁.

critical是一个同步结构,旁边master,atomic和其他人.同步构造用于防止竞争条件并在执行事务时带来顺序.

  • critical通过阻止在所谓的争用组中的线程之间同时执行代码来防止竞争条件.这意味着来自所有并行区域的所有线程遇到类似命名的关键构造都会被序列化;
  • atomic通常通过使用特殊的汇编指令将某些简单的内存操作转换为原子操作.Atomics作为一个不易破坏的单元立即完成.例如,一个线程从某个位置读取原子,与另一个线程对同一位置的原子写入同时发生,将返回旧值或更新值,但绝不会出现某种类型的中间混搭来自旧值和新值的位;
  • master单独输出一个代码块,由主线程(ID为0的线程)执行.与此不同single,构造末尾没有隐式屏障,并且不要求所有线程都必须遇到master构造.此外,缺少隐式屏障意味着master不会刷新线程的共享内存视图(这是OpenMP的一个重要但非常难以理解的部分).master基本上是一个简写if (omp_get_thread_num() == 0) { ... }.

critical它是一个非常通用的构造,因为它能够在程序代码的非常不同的部分中序列化不同的代码段,即使在不同的并行区域中也是如此(仅在嵌套并行性的情况下很重要).每个critical构造都有一个可选的名称,紧接在括号中提供.匿名关键构造共享相同的特定于实现的名称.一旦线程进入这样的构造,任何其他遇到另一个同名构造的线程将被搁置,直到原始线程退出其构造.然后序列化过程继续其余的线程.

以下概念的说明如下.以下代码:

#pragma omp parallel num_threads(3)
{
   foo();
   bar();
   ...
}
Run Code Online (Sandbox Code Playgroud)

结果如下:

thread 0: -----< foo() >< bar() >-------------->
thread 1: ---< foo() >< bar() >---------------->
thread 2: -------------< foo() >< bar() >------>
Run Code Online (Sandbox Code Playgroud)

(线程2故意是后来者)

在构造中进行foo();调用single:

#pragma omp parallel num_threads(3)
{
   #pragma omp single
   foo();
   bar();
   ...
}
Run Code Online (Sandbox Code Playgroud)

结果如下:

thread 0: ------[-------|]< bar() >----->
thread 1: ---[< foo() >-|]< bar() >----->
thread 2: -------------[|]< bar() >----->
Run Code Online (Sandbox Code Playgroud)

这里[ ... ]表示single构造的范围,并且|是其末尾的隐含障碍.注意后来者线程2如何使所有其他线程等待.线程1执行foo()调用,例如OpenMP运行时选择将作业分配给第一个线程以遇到构造.

添加nowait子句可能会删除隐式屏障,从而导致类似:

thread 0: ------[]< bar() >----------->
thread 1: ---[< foo() >]< bar() >----->
thread 2: -------------[]< bar() >---->
Run Code Online (Sandbox Code Playgroud)

foo();匿名critical构造中进行调用:

#pragma omp parallel num_threads(3)
{
   #pragma omp critical
   foo();
   bar();
   ...
}
Run Code Online (Sandbox Code Playgroud)

结果如下:

thread 0: ------xxxxxxxx[< foo() >]< bar() >-------------->
thread 1: ---[< foo() >]< bar() >------------------------->
thread 2: -------------xxxxxxxxxxxx[< foo() >]< bar() >--->
Run Code Online (Sandbox Code Playgroud)

用于xxxxx...显示线程在进入其自己的构造之前等待其他线程执行同名的关键构造的时间.

不同名称的关键构造不会彼此同步.例如:

#pragma omp parallel num_threads(3)
{
   if (omp_get_thread_num() > 1) {
     #pragma omp critical(foo2)
     foo();
   }
   else {
     #pragma omp critical(foo01)
     foo();
   }
   bar();
   ...
}
Run Code Online (Sandbox Code Playgroud)

结果如下:

thread 0: ------xxxxxxxx[< foo() >]< bar() >---->
thread 1: ---[< foo() >]< bar() >--------------->
thread 2: -------------[< foo() >]< bar() >----->
Run Code Online (Sandbox Code Playgroud)

现在,线程2不与其他线程同步,因为它的关键构造以不同的方式命名,因此会产生潜在危险的同时调用foo().

另一方面,无论代码位于何处,匿名关键构造(以及通常具有相同名称的构造)都会彼此同步:

#pragma omp parallel num_threads(3)
{
   #pragma omp critical
   foo();
   ...
   #pragma omp critical
   bar();
   ...
}
Run Code Online (Sandbox Code Playgroud)

以及由此产生的执行时间表:

thread 0: ------xxxxxxxx[< foo() >]< ... >xxxxxxxxxxxxxxx[< bar() >]------------>
thread 1: ---[< foo() >]< ... >xxxxxxxxxxxxxxx[< bar() >]----------------------->
thread 2: -------------xxxxxxxxxxxx[< foo() >]< ... >xxxxxxxxxxxxxxx[< bar() >]->
Run Code Online (Sandbox Code Playgroud)