嵌套并行:为什么只有主线程运行并执行四次并行 for 循环?

For*_*est 4 c parallel-processing multithreading openmp

我的代码:

#include <cstdio>
#include "omp.h"

int main() {
    omp_set_num_threads(4);  
    #pragma omp parallel
    {
        #pragma omp parallel for 
        for (int i = 0; i < 6; i++)
        {
            printf("i = %d, I am Thread %d\n", i, omp_get_thread_num());
        }    
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我得到的输出:

i = 0, I am Thread 0
i = 1, I am Thread 0
i = 2, I am Thread 0
i = 0, I am Thread 0
i = 1, I am Thread 0
i = 0, I am Thread 0
i = 1, I am Thread 0
i = 2, I am Thread 0
i = 2, I am Thread 0
i = 3, I am Thread 0
i = 0, I am Thread 0
i = 1, I am Thread 0
i = 3, I am Thread 0
i = 4, I am Thread 0
i = 5, I am Thread 0
i = 2, I am Thread 0
i = 3, I am Thread 0
i = 4, I am Thread 0
i = 5, I am Thread 0
i = 3, I am Thread 0
i = 4, I am Thread 0
i = 5, I am Thread 0
i = 4, I am Thread 0
i = 5, I am Thread 0
Run Code Online (Sandbox Code Playgroud)

添加“并行”是问题的原因,但我不知道如何解释。

我的问题是:为什么只有主线程并运行 for 循环四次?

dre*_*ash 5

默认情况下,nested parallelismdisabled。尽管如此,您可以通过以下任一方式显式启用 nested parallelism

   omp_set_nested(1);
Run Code Online (Sandbox Code Playgroud)

或者通过将OMP_NESTED环境变量设置为 true。

同样从OpenMP 标准中我们知道:

当线程遇到并行构造时,会创建一组线程来执行并行区域。 遇到并行构造的线程成为新团队的主线程,在新并行区域的持续时间内线程编号为零。新团队中的所有线程,包括主线程,都执行该区域。创建团队后,团队中的线程数在该并行区域的持续时间内保持不变。

您可以从源代码阅读以下内容。

OpenMP 并行区域可以相互嵌套。如果禁用嵌套并行性,则由在并行区域内遇到并行构造的线程创建的新组仅由遇到的线程组成。如果启用了嵌套并行性,则新团队可能由多个线程组成。

这就解释了为什么当您添加第二个原因parallel region只有一个线程队执行封闭的代码(即,用于循环)。换句话说,从第一parallel region4线程的创建,每个这些线程时遇到的第二个parallel region 将创建一支新球队,成为球队的主人(即,将有ID=0新创建的团队内)。但是,由于您没有明确启用嵌套并行性,因此每个团队仅由一个线程组成。因此,4各有一个线程的团队将执行for循环。因此,您将得到以下语句:

printf("i = %d, I am Thread %d\n", i, omp_get_thread_num());
Run Code Online (Sandbox Code Playgroud)

正在打印6 x 4 = 24 times循环迭代的总数乘以跨4团队的线程总数)。下图提供了该流程的可视化:

在此处输入图片说明

如果printf在第一个和第二个之间添加一个语句parallel region,如下:

int main() {

    omp_set_num_threads(4);
    #pragma omp parallel
    {
       printf("Before nested parallel region:  I am Thread{%d}\n", omp_get_thread_num());
       #pragma omp parallel for // Adding "parallel" is the cause of the problem, but I don't know how to explain it.
       for (int i = 0; i < 6; i++)
       {
           printf("i = %d, I am Thread %d\n", i, omp_get_thread_num());
       }
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

您会得到类似于以下输出的内容(请记住,第一4行的输出顺序是不确定的)。

Before nested parallel region:  I am Thread{1}
Before nested parallel region:  I am Thread{0}
Before nested parallel region:  I am Thread{2}
Before nested parallel region:  I am Thread{3}
i = 0, I am Thread 0
i = 0, I am Thread 0
i = 0, I am Thread 0
(...)
i = 5, I am Thread 0
Run Code Online (Sandbox Code Playgroud)

这意味着在第一个parallel region(但仍然在第二个并行区域之外)有一个由 4 个线程组成的团队——IDs0到到3——并行执行。因此,每个线程都将执行以下printf语句:

printf("I am Thread outside the nested region {%d}\n", omp_get_thread_num());
Run Code Online (Sandbox Code Playgroud)

并为omp_get_thread_num()方法调用显示不同的值。

如前所述,嵌套并行被禁用。因此,当这些线程中的每一个遇到第二个时parallel region,每个线程都会创建一个新团队并成为主节点(即,ID=0在新创建的团队中拥有 )。—— 也是唯一的成员 —— 该团队的成员。因此,为什么声明

 printf("i = %d, I am Thread %d\n", i, omp_get_thread_num());
Run Code Online (Sandbox Code Playgroud)

在循环内部,输出 always (..) I am Thread 0,因为omp_get_thread_num()此上下文中的方法将返回 always 0。然而,即使该方法omp_get_thread_num()正在返回0,也并不意味着代码是按ID=0顺序执行的(由带有 的线程),而是每个团队的每个主人都在4返回他们的ID=0.

如果您启用了嵌套并行性,您将拥有如下图所示的流程:

在此处输入图片说明

为简单起见,省略了线程1to的执行3,但它与线程 0 相同。

因此,从第一个开始parallel region4就创建了一个带线程的团队。在遇到上一个parallel region团队的下一个每个线程后,每个线程都会创建一个新的4线程团队,所以目前我们总共有164团队的线程。最后,每个团队将执行整个for循环。但是,因为您有一个#pragma omp parallel for构造函数,所以for循环的迭代将在每个团队内的线程之间分配。

请记住,在上图中,我假设static循环之间的迭代具有特定的循环分布,我并不是暗示循环迭代将始终在 OpenMP 标准的所有实现中如此划分。