OpenMP的区别在于:

#pragma omp parallel sections
{
    #pragma omp section
    {
       fct1();
    }
    #pragma omp section
    {
       fct2();
    }
}

以及:

#pragma omp parallel 
{
    #pragma omp single
    {
       #pragma omp task
       fct1();
       #pragma omp task
       fct2();
    }
}

我不确定第二个代码是否正确...

推荐答案

任务和节之间的区别在于代码将执行的时间范围.节包含在sections构造中,并且(除非指定了nowait子句)线程在执行完所有节之前不会离开它:

                 [    sections     ]
Thread 0: -------< section 1 >---->*------
Thread 1: -------< section 2      >*------
Thread 2: ------------------------>*------
...                                *
Thread N-1: ---------------------->*------

在这里,N个线程遇到了一个包含两个部分的sections构造,第二个部分比第一个部分花费更多的时间.前两个线程分别执行一个部分.其他N-2个线程只是在节构造末尾的隐式屏障处等待(这里显示为*).

只要有可能,任务就会在所谓的任务调度点排队并执行.在某些情况下,可以允许运行库在线程之间移动任务,即使是在线程生命周期的中段.这样的任务被称为未绑定的,并且未绑定的任务可能会在一个线程中开始执行,然后在某个调度点,它可能会被运行时迁移到另一个线程.

尽管如此,任务和部分在很多方面都是相似的.例如,以下两个代码片段实现了基本上相同的结果:

// sections
...
#pragma omp sections
{
   #pragma omp section
   foo();
   #pragma omp section
   bar();
}
...

// tasks
...
#pragma omp single nowait
{
   #pragma omp task
   foo();
   #pragma omp task
   bar();
}
#pragma omp taskwait
...

taskwait的工作原理与barrier非常相似,但对于任务来说——它确保当前的执行流将暂停,直到所有排队的任务都已执行.它是一个调度点,即允许线程处理任务.single构造是必需的,因此任务只能由一个线程创建.如果没有single构造,每个任务将被创建num_threads次,这可能不是我们想要的.single构造中的nowait子句指示其他线程不要等到single构造被执行(即移除single构造末尾的隐式屏障).因此,他们立即达到taskwait并开始处理任务.

为清楚起见,这里所示的taskwait是一个明确的调度点.也有隐式的调度点,最明显的是在屏障同步内部,无论是显式的还是隐式的.因此,上面的代码也可以简单地写成:

// tasks
...
#pragma omp single
{
   #pragma omp task
   foo();
   #pragma omp task
   bar();
}
...

下面是一个可能的场景,如果有三个线程:

               +--+-->[ task queue ]--+
               |  |                   |
               |  |       +-----------+
               |  |       |
Thread 0: --< single >-|  v  |-----
Thread 1: -------->|< foo() >|-----
Thread 2: -------->|< bar() >|-----

这里显示的| ... |是调度点的动作(taskwait指令或隐式屏障).基本上,线程12挂起它们在该点上正在做的事情,并开始处理队列中的任务.处理完所有任务后,线程将恢复正常的执行流.注意,线程12可能在线程0退出single构造之前到达调度点,因此不需要对齐左侧的|(如上图所示).

甚至在其他线程能够请求任务之前,线程1也可能能够完成对foo()任务的处理并请求另一个任务.因此foo()bar()可能由同一线程执行:

               +--+-->[ task queue ]--+
               |  |                   |
               |  |      +------------+
               |  |      |
Thread 0: --< single >-| v             |---
Thread 1: --------->|< foo() >< bar() >|---
Thread 2: --------------------->|      |---

如果线程2来得太晚,那么单独的线程也可能执行第二个任务:

               +--+-->[ task queue ]--+
               |  |                   |
               |  |      +------------+
               |  |      |
Thread 0: --< single >-| v < bar() >|---
Thread 1: --------->|< foo() >      |---
Thread 2: ----------------->|       |---

在某些情况下,编译器或OpenMP运行时甚至可能完全绕过任务队列,以串行方式执行任务:

Thread 0: --< single: foo(); bar() >*---
Thread 1: ------------------------->*---
Thread 2: ------------------------->*---

如果区域代码中没有任务调度点,OpenMP运行时可能会在它认为合适的时候启动任务.例如,有可能所有任务都被推迟到parallel区域末尾的屏障.

C++相关问答推荐

设计处理各种数据类型的方法和数据 struct

为什么下面的C代码会进入无限循环?

C中是否有语法可以直接初始化一个常量文本常量数组的 struct 成员?

如何在Visual Studio代码中关闭此函数名称显示功能?

LONG_DOUBLE_T是否存在(标准C:C23)

为什么即使在强制转换时,此代码也会溢出?

非常大的数组的大小

`#if`条件中是否允许`sizeof`?

轮询libusb_pollfd struct 列表的正确方式是什么?

如何在C中打印包含扫描字符和整数的语句?

函数的限制限定指针参数允许优化调用方函数吗?

解决S随机内存分配问题,实现跨进程高效数据共享

S和查尔有什么不同[1]?

C中的回文数字

如何在C中定义指向函数的指针并将该指针赋给函数?

C中的空指针是什么(_N)?

try 判断长整数是否为素数

Matlab/Octave对conv2函数使用哪种方法?

I';我试着从.txt文件中读取文本,并用c计算其中的单词数量

计算 e^x 很好.但不是 x 的罪