1、fork/join并行执行模式
OpenMP是一个编译器指令和库函数的集合,主要是为共享存储计算机上的并行程序设计使用的。
标准并行模式(fork/join并行模式)执行代码的基本思想是,程序开始只有一个主线程,程序中的串行部分都由主线程执行,并行的部分通过派生其他线程来执行,如果串行部分没有执行结束,接下来的串行部分必须要等待子线程中并行部分执行完毕才能继续推进。
例如:

int main(int argc, char* argv[])
{
      clock_t t1 = clock();
      #pragma omp parallel for
      for ( int j = 0; j < 2; j++ ){
          test();
      }
      clock_t t2 = clock();
      printf("Total time = %d\n", t2‐t1);
   
      test();
      return 0;
}

在没有执行完for循环中的代码之前,后面的clock_t t2 = clock();不会执行,如果和调用线程创建函数相比,它相当于先创建线程,并等待线程执行完毕,所以这种模式中在主线程里面创建的线程并没有和主线程并行运行

2、OpenMP指令和库函数介绍
在C/C++中,OpenMP指令使用的格式为

#pragma omp 指令 [字句][字句]...

前面提到的parallel for就是一条指令,有些书中也将OpenMP的“指令”叫做“编译指导语
句”,后面的子句是可选的。
例如:

#pragma omp parallel private(i, j)

parallel是指令,private是字句

(1)OpenMP指令有以下一些:
parallel,用在一个代码段之前,表示这段代码将被多个线程并行执行;
for,用于 for 循环之前,将循环分配到多个线程中并行执行,必须保证每次循环之间无相关性(循环间无数据竞争);
parallel for, parallel 和 for 语句的结合,也是用在一个 for 循环之前,表示 for 循
环的代码将被多个线程并行执行;
sections,用在可能会被并行执行的代码段之前;
parallel sections,parallel 和 sections 两个语句的结合;
critical,用在一段代码临界区之前;
single,用在一段只被单个线程执行的代码段之前,表示后面的代码段将被单线程执行;
barrier,用于并行区内代码的线程同步,所有线程执行到 barrier 时要停止,直到所有线程都执行到 barrier 时才继续往下执行;
atomic,用于指定一块内存区域被制动更新;
master,用于指定一段代码块由主线程执行;
ordered,用于指定并行区域的循环按顺序执行;
threadprivate, 用于指定一个变量是线程私有的。

(2)OpenMP有如下库函数
omp_get_num_procs, 返回运行本线程的多处理机的处理器个数;
omp_get_num_threads, 返回当前并行区域中的活动线程个数;
omp_get_thread_num, 返回线程号;
omp_set_num_threads, 设置并行执行代码时的线程个数;
omp_init_lock, 初始化一个简单锁;
omp_set_lock,上锁操作;
omp_unset_lock,解锁操作,要和 omp_set_lock 函数配对使用;
omp_destroy_lock,omp_init_lock 函数的配对操作函数,关闭一个锁。

(3)OpenMP有如下库函数子句
private, 指定每个线程都有它自己的变量私有副本。
firstprivate,指定每个线程都有它自己的变量私有副本,并且变量要被继承主线程中的初值。
lastprivate,主要是用来指定将线程中的私有变量的值在并行处理结束后复制回主线程中的对应变量。
reduce,用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的运算。
nowait,忽略指定中暗含的等待
num_threads,指定线程的个数
schedule,指定如何调度 for 循环迭代
shared,指定一个或多个变量为多个线程间的共享变量
ordered,用来指定 for 循环的执行要按顺序执行
copyprivate,用于 single 指令中的指定变量为多个线程的共享变量
copyin,用来指定一个 threadprivate 的变量的值要用主线程的值进行初始化。
default,用来指定并行处理区域内的变量的使用方式,缺省是 shared。

3、parallel指令用法
parallel 是用来构造一个并行块的,也可以使用其他指令如 for、sections 等和它配合使用

#pragma omp parallel [for|sections][子句][子句][...]
{
    ...
}

parallel 语句后面要跟一个大括号对将要并行执行的代码括起来

#pragma omp parallel   
{
    printf(“Hello, World!\n”);
}

根据处理器核心数创建线程,并执行;同时,也可以指定线程数:

#pragma omp parallel num_threads(8)  
{
    printf(“Hello, World!\n”);
}

parallel 指令是用来为一段代码创建多个线程来执行它的。parallel 块中的每行代码都被多个线程重复执行。和传统的创建线程函数比起来,相当于为一个线程入口函数重复调用创建线程函数来创建线程并等待线程执行完。

4、for指令的使用方法
for 指令则是用来将一个 for 循环分配到多个线程中执行。for 指令一般可以和 parallel 指令合起来形成 parallel for 指令使用,也可以单独用在 parallel 语句的并行块中。
(1)parallel for形式

int j = 0;
#pragma omp parallel for
      for ( j = 0; j < 4; j++ )
      {
          printf(“j = %d, ThreadId = %d\n”, j, omp_get_thread_num());
      }

(2)分开形式

#pragma omp parallel   
{
#pragma omp for
      for ( j = 0; j < 4; j++ )
      {
          printf(“j = %d, ThreadId = %d\n”, j, omp_get_thread_num());
      }
}

for 指令要和 parallel 指令结合起来使用!
例如:

int j = 0;
#pragma omp for
      for ( j = 0; j < 4; j++ )
      {
          printf(“j = %d, ThreadId = %d\n”, j, omp_get_thread_num());
      }

这种形式不能并行执行!

5、sections 和 section 指令的用法
section 语句是用在 sections 语句里用来将 sections 语句里的代码划分成几个不同的段,每段都并行执行。用法如下:

#pragma omp [parallel] sections [子句]
{
    #pragma omp section
    {
             代码块
    }   
}

例如:

void main(int argc, char *argv)
{
#pragma omp parallel sections {
#pragma omp section
     printf(“section 1 ThreadId = %d\n”, omp_get_thread_num());
#pragma omp section   
     printf(“section 2 ThreadId = %d\n”, omp_get_thread_num());
#pragma omp section
     printf(“section 3 ThreadId = %d\n”, omp_get_thread_num());
#pragma omp section   
     printf(“section 4 ThreadId = %d\n”, omp_get_thread_num());
}

使用 section 语句时(section 之间并行),需要注意的是这种方式需要保证各个 section 里的代码执行时间相差不大,否则某个 section 执行时间比其他 section 过长就达不到并行执行的效果了。

如果写成这样:

void main(int argc, char *argv)
{
#pragma omp parallel {
#pragma omp sections
{
#pragma omp section
        printf(“section 1 ThreadId = %d\n”, omp_get_thread_num());
#pragma omp section   
        printf(“section 2 ThreadId = %d\n”, omp_get_thread_num());
}
#pragma omp sections
{
#pragma omp section
        printf(“section 3 ThreadId = %d\n”, omp_get_thread_num());
#pragma omp section   
        printf(“section 4 ThreadId = %d\n”, omp_get_thread_num());
}
}

这种方式和前面那种方式的区别是,两个 sections 语句是串行执行的,即第二个 sections 语
句里的代码要等第一个 sections 语句里的代码执行完后才能执行。
所以sections间串行,section间并行。
用 for 语句来分摊是由系统自动进行,只要每次循环间没有时间上的差距,那么分摊是很均匀的,使用 section 来划分线程是一种手工划分线程的方式,最终并行性的好坏得依赖于程序员。

标签: none

评论已关闭