Fork me on GitHub

OpenMP的介绍与使用

参考

OpenMP多线程

背景

OpenMP并行执行的程序要全部结束后才能执行后面的非并行部分的代码, 这就是标准的并行模式fork/join式并行模式,共享存储式并行程序就是使用fork/join式并行的。标准并行模式执行代码的基本思想是,程序开始时只有一个主线程,程序中的串行部分都由主线程执行,并行的部分是通过派生其他线程来执行,但是如果并行部分没有结束时是不会执行串行部分的.

OpenMP指令和库函数

指令

  • parallel: 用在一个代码段之前,表示这段代码将被多个线程并行执行。
  • for: 用于for循环之前,将循环分配到多个线程中并行执行,必须保证每次循环之间无相关。
  • parallel for: parallel 和 for语句的结合,也是用在一个for循环之前,表示for循环的代码将被多个线程并行执行。
  • sections: 用在可能会被并行执行的代码段之前。
  • parallel sections: parallel和sections两个语句的结合。
  • critical,用在一段代码临界区之前。
  • single,用在一段只被单个线程执行的代码段之前,表示后面的代码段将被单线程执行。
  • flush,更新并行区域里的共享变量进行的修改,确保并行的多线程对共享变量的读操作是最新值。
  • barrier,用于并行区内代码的线程同步,所有线程执行到barrier时要停止,直到所有线程都执行到barrier时才继续往下执行。
  • atomic,用于指定一块内存区域被自动更新。
  • master,用于指定一段代码块由主线程执行。
  • ordered, 用于指定并行区域的循环按顺序执行。
  • threadprivate, 用于指定一个变量是线程私有的。

库函数

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函数的配对操作函数,关闭一个锁。

子句

OpenMP的子句有以下一些

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

如何使用

  • cmakelist.txt
# openMP
FIND_PACKAGE( OpenMP REQUIRED)
if(OPENMP_FOUND)
message("OPENMP FOUND")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
set(CMAKE_SHARE_LINKER_FLAGS "${CMAKE_SHARE_LINKER_FLAGS} ${OpenMP_SHARE_LINKER_FLAGS}")
endif()
  • 头文件
#include <omp.h>

omp parallel语句

#pragma omp parallel

  • 代码
#pragma omp parallel
{
  printf("hello world! \n");
}

结果

hello world! 
hello world! 
hello world! 
hello world!
  • 代码
#pragma omp parallel num_threads(8)
{
  printf("hello world!, thread id: %d \n", omp_get_thread_num());
}

结果

hello world!, thread id: 1 
hello world!, thread id: 3 
hello world!, thread id: 4 
hello world!, thread id: 5 
hello world!, thread id: 6 
hello world!, thread id: 7 
hello world!, thread id: 0 
hello world!, thread id: 2

#pragma omp parallel for

  • 代码
#pragma omp parallel for
for (size_t i = 0; i < 4; ++i)
{
  printf("i: %d, thread id: %d \n", i, omp_get_thread_num());
}

结果

i: 3, thread id: 3 
i: 0, thread id: 0 
i: 2, thread id: 2 
i: 1, thread id: 1 

注意: 只有for和parallel同时使用才有并行效果.

#pragma omp parallel sections

  • 代码
[toc]#pragma omp parallel sections
{
  #pragma omp section
    printf ("section0 thread id: %d \n", omp_get_thread_num());
  #pragma omp section
    printf ("section1 thread id: %d \n", omp_get_thread_num());
  #pragma omp section
    printf ("section2 thread id: %d \n", omp_get_thread_num());
  #pragma omp section
    printf ("section3 thread id: %d \n", omp_get_thread_num());      
}

结果


[TOC]

# 参考

[OpenMP多线程](<https://blog.csdn.net/Ben_Ben_Niao/article/details/52833895>)



# 背景

OpenMP并行执行的程序要全部结束后才能执行后面的非并行部分的代码, 这就是标准的并行模式**fork/join式并行模式**,共享存储式并行程序就是使用fork/join式并行的。标准并行模式执行代码的基本思想是,程序开始时只有一个主线程,程序中的串行部分都由主线程执行,并行的部分是通过派生其他线程来执行,但是如果并行部分没有结束时是不会执行串行部分的.



# OpenMP指令和库函数

## 指令

+ **parallel**: 用在一个代码段之前,表示这段代码将被多个线程并行执行。
+ **for**: 用于for循环之前,将循环分配到多个线程中并行执行,必须保证每次循环之间无相关。
+ **parallel for**:  parallel 和 for语句的结合,也是用在一个for循环之前,表示for循环的代码将被多个线程并行执行。
+ **sections**: 用在可能会被并行执行的代码段之前。
+ **parallel sections**: parallel和sections两个语句的结合。
+ critical,用在一段代码临界区之前。
+ single,用在一段只被单个线程执行的代码段之前,表示后面的代码段将被单线程执行。
+ flush,更新并行区域里的共享变量进行的修改,确保并行的多线程对共享变量的读操作是最新值。
+ barrier,用于并行区内代码的线程同步,所有线程执行到barrier时要停止,直到所有线程都执行到barrier时才继续往下执行。
+ atomic,用于指定一块内存区域被自动更新。
+ master,用于指定一段代码块由主线程执行。
+ ordered, 用于指定并行区域的循环按顺序执行。
+ threadprivate, 用于指定一个变量是线程私有的。

## 库函数

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函数的配对操作函数,关闭一个锁。

## 子句

OpenMP的子句有以下一些

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



# 如何使用

+ cmakelist.txt

```sh
# openMP
FIND_PACKAGE( OpenMP REQUIRED)
if(OPENMP_FOUND)
message("OPENMP FOUND")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
set(CMAKE_SHARE_LINKER_FLAGS "${CMAKE_SHARE_LINKER_FLAGS} ${OpenMP_SHARE_LINKER_FLAGS}")
endif()
  • 头文件
#include <omp.h>

omp parallel语句

#pragma omp parallel

  • 代码
#pragma omp parallel
{
  printf("hello world! \n");
}

结果

hello world! 
hello world! 
hello world! 
hello world!
  • 代码
#pragma omp parallel num_threads(8)
{
  printf("hello world!, thread id: %d \n", omp_get_thread_num());
}

结果

hello world!, thread id: 1 
hello world!, thread id: 3 
hello world!, thread id: 4 
hello world!, thread id: 5 
hello world!, thread id: 6 
hello world!, thread id: 7 
hello world!, thread id: 0 
hello world!, thread id: 2

#pragma omp parallel for

  • 代码
#pragma omp parallel for
for (size_t i = 0; i < 4; ++i)
{
  printf("i: %d, thread id: %d \n", i, omp_get_thread_num());
}

结果

i: 3, thread id: 3 
i: 0, thread id: 0 
i: 2, thread id: 2 
i: 1, thread id: 1 

注意: 只有for和parallel同时使用才有并行效果.

#pragma omp parallel sections

  • 代码
[toc]#pragma omp parallel sections
{
  #pragma omp section
    printf ("section0 thread id: %d \n", omp_get_thread_num());
  #pragma omp section
    printf ("section1 thread id: %d \n", omp_get_thread_num());
  #pragma omp section
    printf ("section2 thread id: %d \n", omp_get_thread_num());
  #pragma omp section
    printf ("section3 thread id: %d \n", omp_get_thread_num());      
}

结果

section0 thread id: 3 
section3 thread id: 3 
section1 thread id: 0 
section2 thread id: 1
  • 代码
#pragma omp parallel 
{
  #pragma omp sections
  {
    #pragma omp section
      printf ("section0 thread id: %d \n", omp_get_thread_num());
    #pragma omp section
      printf ("section1 thread id: %d \n", omp_get_thread_num());
  }

  #pragma omp sections
  {
    #pragma omp section
      printf ("section2 thread id: %d \n", omp_get_thread_num());
    #pragma omp section
      printf ("section3 thread id: %d \n", omp_get_thread_num());      
  }
}
```# c++之OpenMP使用

[TOC]

# 参考

[OpenMP多线程](<https://blog.csdn.net/Ben_Ben_Niao/article/details/52833895>)



# 背景

OpenMP并行执行的程序要全部结束后才能执行后面的非并行部分的代码, 这就是标准的并行模式**fork/join式并行模式**,共享存储式并行程序就是使用fork/join式并行的。标准并行模式执行代码的基本思想是,程序开始时只有一个主线程,程序中的串行部分都由主线程执行,并行的部分是通过派生其他线程来执行,但是如果并行部分没有结束时是不会执行串行部分的.



# OpenMP指令和库函数

## 指令

+ **parallel**: 用在一个代码段之前,表示这段代码将被多个线程并行执行。
+ **for**: 用于for循环之前,将循环分配到多个线程中并行执行,必须保证每次循环之间无相关。
+ **parallel for**:  parallel 和 for语句的结合,也是用在一个for循环之前,表示for循环的代码将被多个线程并行执行。
+ **sections**: 用在可能会被并行执行的代码段之前。
+ **parallel sections**: parallel和sections两个语句的结合。
+ critical,用在一段代码临界区之前。
+ single,用在一段只被单个线程执行的代码段之前,表示后面的代码段将被单线程执行。
+ flush,更新并行区域里的共享变量进行的修改,确保并行的多线程对共享变量的读操作是最新值。
+ barrier,用于并行区内代码的线程同步,所有线程执行到barrier时要停止,直到所有线程都执行到barrier时才继续往下执行。
+ atomic,用于指定一块内存区域被自动更新。
+ master,用于指定一段代码块由主线程执行。
+ ordered, 用于指定并行区域的循环按顺序执行。
+ threadprivate, 用于指定一个变量是线程私有的。

## 库函数

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函数的配对操作函数,关闭一个锁。

## 子句

OpenMP的子句有以下一些

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



# 如何使用

+ cmakelist.txt

```sh
# openMP
FIND_PACKAGE( OpenMP REQUIRED)
if(OPENMP_FOUND)
message("OPENMP FOUND")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
set(CMAKE_SHARE_LINKER_FLAGS "${CMAKE_SHARE_LINKER_FLAGS} ${OpenMP_SHARE_LINKER_FLAGS}")
endif()
  • 头文件
#include <omp.h>

omp parallel语句

#pragma omp parallel

  • 代码
#pragma omp parallel
{
  printf("hello world! \n");
}

结果

hello world! 
hello world! 
hello world! 
hello world!
  • 代码
#pragma omp parallel num_threads(8)
{
  printf("hello world!, thread id: %d \n", omp_get_thread_num());
}

结果

hello world!, thread id: 1 
hello world!, thread id: 3 
hello world!, thread id: 4 
hello world!, thread id: 5 
hello world!, thread id: 6 
hello world!, thread id: 7 
hello world!, thread id: 0 
hello world!, thread id: 2

#pragma omp parallel for

  • 代码
#pragma omp parallel for
for (size_t i = 0; i < 4; ++i)
{
  printf("i: %d, thread id: %d \n", i, omp_get_thread_num());
}

结果

i: 3, thread id: 3 
i: 0, thread id: 0 
i: 2, thread id: 2 
i: 1, thread id: 1 

注意: 只有for和parallel同时使用才有并行效果.

#pragma omp parallel sections

  • 代码
[toc]#pragma omp parallel sections
{
  #pragma omp section
    printf ("section0 thread id: %d \n", omp_get_thread_num());
  #pragma omp section
    printf ("section1 thread id: %d \n", omp_get_thread_num());
  #pragma omp section
    printf ("section2 thread id: %d \n", omp_get_thread_num());
  #pragma omp section
    printf ("section3 thread id: %d \n", omp_get_thread_num());      
}

结果

section0 thread id: 3 
section3 thread id: 3 
section1 thread id: 0 
section2 thread id: 1
  • 代码
#pragma omp parallel 
{
  #pragma omp sections
  {
    #pragma omp section
      printf ("section0 thread id: %d \n", omp_get_thread_num());
    #pragma omp section
      printf ("section1 thread id: %d \n", omp_get_thread_num());
  }

  #pragma omp sections
  {
    #pragma omp section
      printf ("section2 thread id: %d \n", omp_get_thread_num());
    #pragma omp section
      printf ("section3 thread id: %d \n", omp_get_thread_num());      
  }
}

结果

section0 thread id: 0 
section1 thread id: 3 
section2 thread id: 3 
section3 thread id: 3 

omp的线程数量

在具体计算需要使用多少线程时,主要需要考虑以下两点:
1) 当循环次数比较少时,如果分成过多数量的线程来执行,可能使得总运行时间高亍较少线程戒一个线程执行的情况。并且会增加能耗。
2) 如果设置的线程数量进大亍 CPU 核数的话,那么存在着大量的任务切换和调度等开销,也会降低整体效率。

假设一个需要劢态设置线程数的需求为:
1、 以多个线程运行时的每个线程运行的循环次数丌低亍 4 次
2、 总的运行线程数最大丌超过 2 倍 CPU 核数

/** 计算循环迭代需要的线程数量
根据循环迭代次数和 CPU 核数及一个线程最少需要的循环迭代次数
来计算出需要的线程数量,计算出的最大线程数量丌超过 CPU 核数
@param int n - 循环迭代次数
@param int min_n - 单个线程需要的最少迭代次数
@return int - 线程数量
*/
int dtn(int n, int min_n)
{
  int max_tn = n / min_n;
  int tn = max_tn > g_ncore ? g_ncore : max_tn; //tn 表示要设置的线程数量
  if ( tn < 1 )
  {
  tn = 1;
  }
  return tn;
}

结果

section0 thread id: 0 
section1 thread id: 3 
section2 thread id: 3 
section3 thread id: 3 

omp的线程数量

在具体计算需要使用多少线程时,主要需要考虑以下两点:
1) 当循环次数比较少时,如果分成过多数量的线程来执行,可能使得总运行时间高亍较少线程戒一个线程执行的情况。并且会增加能耗。
2) 如果设置的线程数量进大亍 CPU 核数的话,那么存在着大量的任务切换和调度等开销,也会降低整体效率。

假设一个需要劢态设置线程数的需求为:
1、 以多个线程运行时的每个线程运行的循环次数丌低亍 4 次
2、 总的运行线程数最大丌超过 2 倍 CPU 核数

/** 计算循环迭代需要的线程数量
根据循环迭代次数和 CPU 核数及一个线程最少需要的循环迭代次数
来计算出需要的线程数量,计算出的最大线程数量丌超过 CPU 核数
@param int n - 循环迭代次数
@param int min_n - 单个线程需要的最少迭代次数
@return int - 线程数量
*/
int dtn(int n, int min_n)
{
  int max_tn = n / min_n;
  int tn = max_tn > g_ncore ? g_ncore : max_tn; //tn 表示要设置的线程数量
  if ( tn < 1 )
  {
  tn = 1;
  }
  return tn;
}

section0 thread id: 3
section3 thread id: 3
section1 thread id: 0
section2 thread id: 1




+ 代码

```cpp
#pragma omp parallel 
{
  #pragma omp sections
  {
    #pragma omp section
      printf ("section0 thread id: %d \n", omp_get_thread_num());
    #pragma omp section
      printf ("section1 thread id: %d \n", omp_get_thread_num());
  }

  #pragma omp sections
  {
    #pragma omp section
      printf ("section2 thread id: %d \n", omp_get_thread_num());
    #pragma omp section
      printf ("section3 thread id: %d \n", omp_get_thread_num());      
  }
}

结果

section0 thread id: 0 
section1 thread id: 3 
section2 thread id: 3 
section3 thread id: 3 

omp的线程数量

在具体计算需要使用多少线程时,主要需要考虑以下两点:
1) 当循环次数比较少时,如果分成过多数量的线程来执行,可能使得总运行时间高亍较少线程戒一个线程执行的情况。并且会增加能耗。
2) 如果设置的线程数量进大亍 CPU 核数的话,那么存在着大量的任务切换和调度等开销,也会降低整体效率。

假设一个需要劢态设置线程数的需求为:
1、 以多个线程运行时的每个线程运行的循环次数丌低亍 4 次
2、 总的运行线程数最大丌超过 2 倍 CPU 核数

/** 计算循环迭代需要的线程数量
根据循环迭代次数和 CPU 核数及一个线程最少需要的循环迭代次数
来计算出需要的线程数量,计算出的最大线程数量丌超过 CPU 核数
@param int n - 循环迭代次数
@param int min_n - 单个线程需要的最少迭代次数
@return int - 线程数量
*/
int dtn(int n, int min_n)
{
  int max_tn = n / min_n;
  int tn = max_tn > g_ncore ? g_ncore : max_tn; //tn 表示要设置的线程数量
  if ( tn < 1 )
  {
  tn = 1;
  }
  return tn;
}
posted @ 2021-06-24 19:32  chrislzy  阅读(1440)  评论(0编辑  收藏  举报