@Marco Bonelli 的答案很准确,我只是想进一步了解幕后发生的事情。
默认情况下,nested parallelism禁用。尽管如此,您可以通过以下任一方式明确启用 nested parallelism:
omp_set_nested(1);
或通过将OMP_NESTED 环境变量设置为true。
同样从OpenMP standard我们知道:
当一个线程遇到一个并行结构时,一组线程是
创建以执行并行区域。
遇到并行构造的线程变为
新团队的主线程,线程号为零
新平行区域的持续时间。新团队中的所有线程,
包括主线程,执行区域。一旦球队在
创建后,团队中的线程数保持不变
该平行区域的持续时间。
来自source,您可以阅读以下内容。
OpenMP 并行区域可以相互嵌套。 如果嵌套
并行性被禁用,然后由一个线程创建的新团队
在平行区域内遇到平行构造包括
只有遇到的线程。如果启用了嵌套并行,
那么新团队可能包含多个线程。
这解释了为什么当你添加第二个parallel region(ie,#pragma omp parallel for num_threads(4))时只有一个线程per 团队执行封闭代码(即 for 循环)。换句话说,从第一个parallel region,4线程被创建,每个线程在遇到第二个parallel region时将创建一个新团队并成为该团队的主人(即,将拥有ID=0在新创建的团队中)。 但是,由于您没有显式启用嵌套并行性,因此这些团队中的每一个都仅由一个线程组成。因此,4 各有一个线程的团队将执行 for 循环。因此,您将有以下语句:
printf("My rank is: %d num is: %d\n",my_rank, i);
正在打印4 x 4 = 16 times(即循环迭代的总数乘以4 团队中的线程总数)。这就是您得到以下输出的原因:
My rank is: 0 num is: 0
My rank is: 0 num is: 1
My rank is: 0 num is: 2
My rank is: 0 num is: 3
My rank is: 2 num is: 0
My rank is: 2 num is: 1
My rank is: 2 num is: 2
My rank is: 2 num is: 3
My rank is: 3 num is: 0
My rank is: 3 num is: 1
My rank is: 3 num is: 2
My rank is: 3 num is: 3
My rank is: 1 num is: 0
My rank is: 1 num is: 1
My rank is: 1 num is: 2
My rank is: 1 num is: 3
下图提供了该流程的可视化:
请记住,在上图中,我假设循环之间的迭代有一定的static 循环分布,我并不是暗示循环迭代将始终像这样在 OpenMP 标准的所有实现中进行划分。
我希望得到以下输出:
My rank is: 0 num is: 0
My rank is: 1 num is: 1
My rank is: 2 num is: 2
My rank is: 3 num is: 3
很明显,您正在寻找的是:
#pragma omp parallel for num_threads(4)
for(int i = 0; i < 4; i++){
printf("My rank is: %d num is: %d\n", omp_get_thread_num(), i);
}
:
#pragma omp parallel for
将创建一个parallel region(如前所述),并且将使用default chunk size和default schedule为该区域的threads分配它所包含的循环的迭代,即通常 static。但请记住,default schedule 在 OpenMP 标准的不同具体实现之间可能会有所不同。
您可以从OpenMP 5.1 阅读更正式的描述:
worksharing-loop 结构指定一个或一个的迭代
更多相关的循环将由线程中的线程并行执行
团队在他们的隐含任务的背景下。 迭代是
分布在团队中已经存在的线程中
执行工作共享循环区域所在的并行区域
绑定。
Moreover,
并行循环结构是指定并行循环的快捷方式
包含带有一个或多个相关联的循环构造的构造
循环,没有其他语句。
或者非正式地,#pragma omp parallel for 是构造函数 #pragma omp parallel 和 #pragma omp for 的组合。