我试图了解 OpenMP 中#pragma omp critical
和#pragma omp single
之间的确切区别:
这些的 Microsoft 定义是:
Single:允许您指定应在单个线程(不一定是主线程)上执行的代码段。
关键:指定代码一次只能在一个线程上执行。
所以这意味着在这两个,代码的确切部分之后将由一个线程执行,其他线程将不会进入该部分,例如,如果我们打印的东西,我们会看到屏幕上的结果一次,对不对?
区别如何?看起来关键照顾执行时间,但不是单一的!但我在实践中没有看到任何区别!这是否意味着一种等待或同步其他线程(不进入该部分)被认为是关键的,但没有什么可以将其他线程保持在单一的?它如何在实践中改变结果?
我很感激,如果任何人都可以澄清这一点,特别是通过一个例子。
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);
将打印
single: 1 -- critical: 4
我希望你现在能更好地看到差异。
为了完整起见,我可以补充一点:
与
single
非常相似,但有两个区别:
将仅由主机执行,而
single
可以由首先到达该区域的线程执行;和
single
在区域完成时具有隐式屏障,其中所有线程等待同步,而没有任何。
atomic
与critical
非常相似,但仅限于选择简单的操作。
我添加了这些精度,因为这两对指令通常是人们倾向于混淆的指令...
single
和critical
属于两个完全不同的 OpenMP 构造类。single
是一个工作共享构造,与for
和sections
一起。工作共享构造用于在线程之间分配一定数量的工作。从某种意义上说,这些构造是“集体的”,在正确的 OpenMP 程序中,所有线程
for
(又名循环构造)在线程之间自动分配循环的迭代-在大多数情况下,所有线程都有工作要做;
sections
在线程之间分配一系列的代码块-一些线程需要工作。这是将for
构造概括为具有 100 次迭代的循环,可以表示为例如 10 个循环部分,每个循环 10 次迭代。
single
挑出一个代码块只能由一个线程执行,通常是第一个遇到它的线程(实现细节)-只有一个线程得到工作。single
在很大程度上等同于sections
只有一个部分。
所有工作共享构造的一个共同特点是在其末端存在一个隐式屏障,该屏障可以通过将nowait
子句添加到相应的 OpenMP 构造来关闭,但是该标准不需要这样的行为,并且在某些 OpenMP 运行时,尽管存在nowait
,屏障可能会继续存在。
critical
是一个同步构造,与、
atomic
等一起使用。同步构造用于防止竞争条件并在执行事物时带来秩序。
critical
通过防止在所谓的争用组中的线程之间同时执行代码来防止竞争条件。这意味着来自所有并行区域的遇到类似名称的关键结构的所有线程被序列化;
atomic
通常通过使用特殊的汇编指令将某些简单的内存操作转换为原子操作。原子作为一个不可的单元一次完成。例如,一个线程从某个位置读取原子,同时另一个线程将原子写入同一位置,将返回旧值或更新值,但绝不会返回旧值和新值的某种中间混搭;
挑出一个代码块,仅由主线程(ID 为 0 的线程)执行。与
single
不同,在构造的末尾没有隐式屏障,也不要求所有线程都必须遇到构造。此外,缺少隐式屏障意味着
基本上不会刷新线程的共享内存视图。
critical
是一个非常通用的构造,因为它能够在程序代码的非常不同的部分中序列化不同的代码片段,即使在不同的并行区域中也是如此 (仅在嵌套并行性的情况下很重要)。每个critical
构造都有一个紧跟其后的括号中提供的可选名称。匿名关键构造共享相同的实现特定名称。一旦一个线程进入这样的构造,任何其他线程
下面是上述概念的说明。
#pragma omp parallel num_threads(3)
{
foo();
bar();
...
}
结果如下:
thread 0: -----< foo() >< bar() >-------------->
thread 1: ---< foo() >< bar() >---------------->
thread 2: -------------< foo() >< bar() >------>
(线程 2 故意是一个后来者)
在single
构造中有foo();
调用:
#pragma omp parallel num_threads(3)
{
#pragma omp single
foo();
bar();
...
}
结果如下:
thread 0: ------[-------|]< bar() >----->
thread 1: ---[< foo() >-|]< bar() >----->
thread 2: -------------[|]< bar() >----->
线程 1 执行foo()
调用,因为示例 OpenMP 运行时选择将作业分配给遇到该构造的第一个线程。
添加一个nowait
子句可能会删除隐式障碍,导致类似:
thread 0: ------[]< bar() >----------->
thread 1: ---[< foo() >]< bar() >----->
thread 2: -------------[]< bar() >---->
在匿名critical
构造中有foo();
调用:
#pragma omp parallel num_threads(3)
{
#pragma omp critical
foo();
bar();
...
}
结果如下:
thread 0: ------xx[< foo() >]< bar() >-------------->
thread 1: ---[< foo() >]< bar() >------------------------->
thread 2: -------------[< foo() >]< bar() >--->
xx...
显示了一个线程在进入自己的构造之前等待其他线程执行相同名称的关键构造所花费的时间。
不同名称的关键结构彼此不同步。例如:
#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: ------xx[< 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: ------xx[< foo() >]< ... >[< bar() >]------------>
thread 1: ---[< foo() >]< ... >[< bar() >]----------------------->
thread 2: -------------[< foo() >]< ... >[< bar() >]->
本站系公益性非盈利分享网址,本文来自用户投稿,不代表边看边学立场,如若转载,请注明出处
评论列表(60条)