什么是OpenMP
- 
    OpenMP全称是”Open specification for Multi-Processing“,它提供了一组用于并行处理的API,跟pthreads一样,适用于share memory场景。由编译器来生成多线程处理的代码,优点是实现简单,缺点是不如pthreads灵活。OpenMP支持fork-join模型,默认提供了join操作,所有线程执行结束了才会返回到主线程。如果其中一个线程异常终止,所有线程都会终止。  
支持多种指令集和操作系统,由非营利性组织管理,多家软硬件厂家参与,包括Arm,AMD,Intel等。
- 
    版本演进  
查看OpenMP版本
echo |cpp -fopenmp -dM |grep -i open
先来看一个OpenMP的例子:
#include <omp.h>
int A[10],B[10],C[10];
// Beginning of parallel section. Fork a team of threads
#pragma omp parallel for num_threads(10)
{
	for (int i=0; i<10; ++i)
		A[i] = B[i]+C[i];
} // All threads join master thread and terminate
- 可以看到OpenMP指令包含下面三个部分:
    - #pragma omp
- directive-name(指令):用哪一种方式做并行(parallel,do,for)
- [clause…] (从句): 可选
 
Parallel Region Construct:并行区构造
parallel指令会创建一组线程,并行执行并行区中的代码。
#pragma omp parallel [clause...] if (scalar_expression) num_threads (integer-expression)
{
    Parallel Region
}
- 支持的从句有:
    - if(scalar_expression): 决定是否以并行方式执行并行区
        - True:并行
- False:串行,一个线程
 
- num_threads(integer_expression): 指定并行区的线程数
- defalut(shared,none):指定默认
        - shared:默认为共享变量
- none:无默认变量类型,每个变量都需要另外指定
 
- shared(list): 指定共享变量列表变量类型
        - 共享变量在内存中只有一份,所有线程都可以访问
- 不特别指定,并行区变量默认为shared
- 请保证共享访问不会冲突
 
- private(list): 指定私有变量列表
        - 每个线程生成一份与该私有变量同类型的数据对象
- 变量需要重新初始化
 
- firstprivate(list):
        - 同private
- 对变量根据主线程中的数据进行初始化
 
- reduction:把每个线程的数据收集起来,然后做操作,效率较高
 #pragma omp parallel for num_threads(8) reduction(+:ans_omp) for (int i=0; i<N; i++) ans_omp += b[i] * b[i]; ans_omp = sqrt(ans_omp);
- if(scalar_expression): 决定是否以并行方式执行并行区
        
- 可以通过以下方式确定线程数,优先级从高到低:
    - 设定IF从句;
- 设定num_threads从句;
- 在进入并行区之前调用库函数omp_set_num_threads()
- 设置OMP_NUM_THREADS环境变量
- 默认使用CPU的核数
 
- 
    并行区可以嵌套 
- 并行区的代码有限制
Work-Sharing Construct:共享工作构造
- 
    共享工作构造指的是把工作进行分解,然后分配给不同的线程。主要有以下三种类型:  
Do/for构造
适用于循环(do-while,for),用来处理数据并行问题
不能创建线程,因此要跟parallel一起使用
for循环需要满足格式要求
- 支持的从句:
    - nowait: Do not synchronize threads at the end of the loop
- schedule: Describes how iterations are divided among threads
        - STATIC
- DYNAMIC: 一个线程执行完一个chunk的任务,会被动态分配另一个chunk
- GUIDED: similar to DYNAMIC except chunk size decreases over time
- RUNTIME: runtime的时候根据OMP_SCHEDULE决定用上面三个中的哪一个调度
- AUTO: compiler决定,效果不太好?
 
 
int chunk = CHUNKSIZE;
int thread = NUM_THREAD;
int N = 1000;
#pragma omp parallel num_thread(thread) shared(a,b,c) private(i)
{
    #pramga omp for schedule(dynamic, chunk) nowait
    for(int i=0; i<N; ++i)
        c[i] = a[i] + b[i];
}
- ordered:  Iterations must be executed as in a serial program
    - 会降低程序运行效率
 
- collapse: 表示紧随其后的 n 层循环会被合并然后并行化
    - https://blog.csdn.net/qq_37206769/article/details/89189780
 
SECTIONS构造
将并行区内的代码块划分为多个section分配执行,适用于non-iterative任务
- 每个section由一个thread执行一次
    - 线程数大于section数目:部分线程空闲
- 线程数小于section数目:部分线程分配多个section
 
可以搭配parallel合成为parallel sections构造
#pragma omp sections [clause...]
{
  #pragma omp section
  	structred_block
  	
  #pragma omp section
  	structred_block 	
}
Single构造
Synchronization Constructs:同步构造
- 
    并行区的执行不是原子的,因此不同线程的操作可能会混在一起。如果需要数据同步,可以进行同步构造。  
任务(task)构造
默认的构造都遵循Fork-Join模式,对任务类型由限制。
允许定义任务及依赖关系,动态调度执行,即动态管理线程池和任务池。
OpenMP in C++
cmake编译
find_package(OpenMP)
add_compile_options(-Wunknown-pragmas)
add_executable(hello src/hello.cpp)
target_link_libraries(hello OpenMP::OpenMP_CXX)
常用库函数
// 设置并行区运行的线程数
void omp_set_num_threads(int)
// 获得并行区运行的线程数
int omp_get_num_threads(void)
// 获得线程编号
int omp_get_thread_num(void)
// 获得openmp wall clock时间(秒)
double omp_get_wtime(void)
// 获得omp_get_wtime时间精度
double omp_get_wtick(void)

 
 
        
      


