"omp single" 和 "omp task" 如何提供并行性?
How do "omp single" and "omp task" provide parallelism?
我对 omp single
和 omp task
指令感到困惑。我已经阅读了几个使用它们的示例。以下示例显示如何使用任务构造来处理链表的元素。
1 #pragma omp parallel
2 {
3 #pragma omp single
4 {
5 for(node* p = head; p; p = p->next)
6 {
7 #pragma omp task
8 process(p);
9 }
10 }
11 }
我没能理解这个例子中的并行性。使用omp single
,只有一个线程会执行与单个构造相关的结构化块,对吗?在这个例子中,第4-10行是与single construct相关的结构块,它只能执行一次,那么为什么我们可以在这个结构块内部使用omp task
呢?如何并行工作?
#pragma omp task
调度一个任务在单线程上,但它可以被其他线程执行。这是使用 OpenMP 任务的一个目的:提供并行性的线程不一定是并行执行计算的线程。
请注意,任务在 调度点 处执行。 omp parallel
部分的末尾是一个调度点。这就是为什么所有其他 OpenMP 线程都应该执行计划任务(只要任务存活时间足够长)。
所以我们一步步来:
当您编写语句#pragma omp parallel 时,并行区域会创建一组线程。
然后你写了#pragma omp single,然后单个线程创建任务,将它们添加到属于团队的队列中。
你终于写了#pragma omp task 。执行此代码段的线程创建一个任务,该任务稍后将被执行,可能由另一个线程将它们添加到属于团队的队列中,以及该团队中的所有线程(可能包括生成任务的线程) .任务执行的确切时间取决于任务调度程序,它对用户不可见。
除了其他答案之外,让我更深入地了解一下执行过程中发生的事情。
1 #pragma omp parallel
2 {
3 #pragma omp single
4 {
5 for(node* p = head; p; p = p->next)
6 {
7 #pragma omp task
8 process(p);
9 }
10 } // barrier of single construct
11 }
在代码中,我标记了在 single
结构末尾引入的屏障。
这是怎么回事:
首先,当遇到 parallel
构造时,主线程生成并行区域并创建一堆工作线程。然后你有 n 个线程 运行 并执行并行区域。
其次,single
构造选择 n 线程中的任何一个并执行 single
构造的大括号内的代码。所有其他 n-1 线程将继续到第 10 行的屏障。在那里,它们将等待最后一个线程赶上并完成屏障同步。当这些线程在那里等待时,它们不仅在浪费时间,还在等待工作的到来。
第三,由 single
构造(“生产者”)选择的线程执行 for
循环,并为每次迭代创建一个新任务。然后将此任务放入任务池中,以便另一个线程(屏障中的线程之一)可以拾取并执行它。一旦生产者创建任务完成,它会加入屏障,如果任务池中还有任务等待执行,它会帮助其他线程执行任务。
第四,一旦所有任务都以这种方式生成并执行,所有线程就完成了,屏障同步就完成了。
我在这里和那里做了一些简化,因为 OpenMP 实现如何执行任务有更多方面,但从概念的角度来看,以上是您可以想到的正在发生的事情,直到您准备深入了解 OpenMP 中任务调度的具体方面 API。
我对 omp single
和 omp task
指令感到困惑。我已经阅读了几个使用它们的示例。以下示例显示如何使用任务构造来处理链表的元素。
1 #pragma omp parallel
2 {
3 #pragma omp single
4 {
5 for(node* p = head; p; p = p->next)
6 {
7 #pragma omp task
8 process(p);
9 }
10 }
11 }
我没能理解这个例子中的并行性。使用omp single
,只有一个线程会执行与单个构造相关的结构化块,对吗?在这个例子中,第4-10行是与single construct相关的结构块,它只能执行一次,那么为什么我们可以在这个结构块内部使用omp task
呢?如何并行工作?
#pragma omp task
调度一个任务在单线程上,但它可以被其他线程执行。这是使用 OpenMP 任务的一个目的:提供并行性的线程不一定是并行执行计算的线程。
请注意,任务在 调度点 处执行。 omp parallel
部分的末尾是一个调度点。这就是为什么所有其他 OpenMP 线程都应该执行计划任务(只要任务存活时间足够长)。
所以我们一步步来:
当您编写语句#pragma omp parallel 时,并行区域会创建一组线程。
然后你写了#pragma omp single,然后单个线程创建任务,将它们添加到属于团队的队列中。
你终于写了#pragma omp task 。执行此代码段的线程创建一个任务,该任务稍后将被执行,可能由另一个线程将它们添加到属于团队的队列中,以及该团队中的所有线程(可能包括生成任务的线程) .任务执行的确切时间取决于任务调度程序,它对用户不可见。
除了其他答案之外,让我更深入地了解一下执行过程中发生的事情。
1 #pragma omp parallel
2 {
3 #pragma omp single
4 {
5 for(node* p = head; p; p = p->next)
6 {
7 #pragma omp task
8 process(p);
9 }
10 } // barrier of single construct
11 }
在代码中,我标记了在 single
结构末尾引入的屏障。
这是怎么回事:
首先,当遇到 parallel
构造时,主线程生成并行区域并创建一堆工作线程。然后你有 n 个线程 运行 并执行并行区域。
其次,single
构造选择 n 线程中的任何一个并执行 single
构造的大括号内的代码。所有其他 n-1 线程将继续到第 10 行的屏障。在那里,它们将等待最后一个线程赶上并完成屏障同步。当这些线程在那里等待时,它们不仅在浪费时间,还在等待工作的到来。
第三,由 single
构造(“生产者”)选择的线程执行 for
循环,并为每次迭代创建一个新任务。然后将此任务放入任务池中,以便另一个线程(屏障中的线程之一)可以拾取并执行它。一旦生产者创建任务完成,它会加入屏障,如果任务池中还有任务等待执行,它会帮助其他线程执行任务。
第四,一旦所有任务都以这种方式生成并执行,所有线程就完成了,屏障同步就完成了。
我在这里和那里做了一些简化,因为 OpenMP 实现如何执行任务有更多方面,但从概念的角度来看,以上是您可以想到的正在发生的事情,直到您准备深入了解 OpenMP 中任务调度的具体方面 API。