omp critical和omp single之间的区别

时间:2015-10-30 17:33:05

标签: parallel-processing openmp

我想了解OpenMP中mBob.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { mp.stop(); } }); #pragma omp critical之间的确切区别:

Microsoft的定义是:

  • Single:允许您指定应执行一段代码 单个线程,不一定是主线程。
  • 严重:指定仅在a的一个线程上执行代码 时间。

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

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

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

2 个答案:

答案 0 :(得分:53)

singlecritical是两个非常不同的事物。如你所说:

  • 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);

将打印

single: 1 -- critical: 4

我希望你现在能更好地看到它们。

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

  • mastersingle非常相似,但有两点不同:
    1. master将由主人执行,而single可以由先到达该区域的任何线程执行;和
    2. single在完成区域时有一个隐含障碍,所有线程都在等待同步,而master没有任何线程。
  • atomiccritical非常相似,但仅限于选择简单的操作。

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

答案 1 :(得分:26)

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

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

所有工作共享构造的一个共同特征是在它们的末尾存在隐式屏障,通过将nowait子句添加到相应的OpenMP构造来关闭可能的屏障,但是标准不需要这样的行为,并且对于一些OpenMP运行时,尽管存在nowait,但障碍仍可能继续存在。错误地排序(即在一些线程中不按顺序)工作共享结构可能因此导致死锁。当障碍存在时,正确的OpenMP程序永远不会死锁。

critical是一个同步构造,与masteratomic和其他构建一起。同步结构用于防止竞争条件并在执行事务时带来顺序。

  • 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();
   ...
}

导致类似:

thread 0: -----< foo() >< bar() >-------------->
thread 1: ---< foo() >< bar() >---------------->
thread 2: -------------< foo() >< bar() >------>

(线程2故意是后来者)

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

#pragma omp parallel num_threads(3)
{
   #pragma omp single
   foo();
   bar();
   ...
}

导致类似:

thread 0: ------[-------|]< bar() >----->
thread 1: ---[< foo() >-|]< bar() >----->
thread 2: -------------[|]< bar() >----->

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

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

thread 0: ------[]< bar() >----------->
thread 1: ---[< foo() >]< bar() >----->
thread 2: -------------[]< bar() >---->

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

#pragma omp parallel num_threads(3)
{
   #pragma omp critical
   foo();
   bar();
   ...
}

导致类似:

thread 0: ------xxxxxxxx[< foo() >]< bar() >-------------->
thread 1: ---[< foo() >]< bar() >------------------------->
thread 2: -------------xxxxxxxxxxxx[< foo() >]< bar() >--->

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

不同名称的关键结构不会彼此同步。 E.g:

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

导致类似:

thread 0: ------xxxxxxxx[< foo() >]< bar() >---->
thread 1: ---[< foo() >]< bar() >--------------->
thread 2: -------------[< foo() >]< bar() >----->

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

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

#pragma omp parallel num_threads(3)
{
   #pragma omp critical
   foo();
   ...
   #pragma omp critical
   bar();
   ...
}

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

thread 0: ------xxxxxxxx[< foo() >]< ... >xxxxxxxxxxxxxxx[< bar() >]------------>
thread 1: ---[< foo() >]< ... >xxxxxxxxxxxxxxx[< bar() >]----------------------->
thread 2: -------------xxxxxxxxxxxx[< foo() >]< ... >xxxxxxxxxxxxxxx[< bar() >]->