OpenMP是⼀种⽤于共享内存并⾏系统的多线程程序设计⽅案,⽀持的编程语⾔包括C、C++和Fortran。OpenMP提供了对并⾏算法的⾼层抽象描述,特别适合在多核CPU机器上的并⾏程序设计。编译器根据程序中添加的pragma指令,⾃动将程序并⾏处理,使⽤OpenMP降低了并⾏编程的难度和复杂度。当编译器不⽀持OpenMP时,程序会退化成普通(串⾏)程序。程序中已有的OpenMP指令不会影响程序的正常编译运⾏。
二、OpenMP的使用 1、获取本机的线程数代码
#include#include int main() { std::cout << "parallel begin:n"; #pragma omp parallel { std::cout << omp_get_thread_num() << std::endl; } std::cout << "n parallel end.n"; std::cin.get(); return 0; }
结果:
说明本机是16线程
omp中使用parallel制导指令标识代码中的并行段
如上个例子中的指令#pragma omp parallel,因此打印输出并行执行。
#pragma omp parallel
{
//线程并行执行的代码
}
parallel表示其后语句将被多个线程并行执行,“#pragma omp parallel”后面的语句(或者,语句块)被称为parallel region。多个线程的执行顺序是不能保证的。
#pragma omp parallel for对for循环进行并行处理,每个核之间是并行执行,各线程顺序不能保证
- 不加openmp指令运行,耗时1.97s
#include#include void test() { int a = 0; for (int i=0;i<100000000;i++) a++; } int main() { clock_t t1 = clock(); for (int i=0;i<8;i++) test(); clock_t t2 = clock(); std::cout<<"time: "<
- 加openmp指令
#include#include void test() { int a = 0; for (int i=0;i<100000000;i++) a++; } int main() { clock_t t1 = clock(); #pragma omp parallel for for (int i=0;i<8;i++) test(); clock_t t2 = clock(); std::cout<<"time: "< #pragma omp parallel for num_threads(4) 指定使用线程数为4
3、解决线程竞争引入的编译导语当多个线程并行执行时,有可能多个线程同时对某变量进行了读写操作,从而导致不可预知的结果。比如下面的例子,对于包含10个整形元素的数组a,我们用for循环求它各个元素之和,并将结果保存在变量sum里。
#includeint main() { int sum = 0; int a[10] = {1,2,3,4,5,6,7,8,9,10}; #pragma omp parallel for for (int i=0;i<10;i++) sum = sum + a[i]; std::cout<<"sum: "< 如果我们注释掉#pragma omp parallel for,让程序先按照传统串行的方式执行,很明显,sum=55。但按照并行方式执行后,sum则会变成其他值,比如在某次运行过程中,sum=49。其原因是,当某线程A执行sum = sum + a[i]的同时,另一线程B正好在更新sum,而此时A还在用旧的sum做累加,于是出现了错误。
基本解决方案
生成一个数组sumArray,其长度为并行执行的线程的个数(默认情况下,该个数等于CPU的核数),在for循环里,让各个线程更新自己线程对应的sumArray里的元素累加到sum里
代码#include#include int main(){ int sum = 0; int a[10] = {1,2,3,4,5,6,7,8,9,10}; int coreNum = omp_get_num_procs();//获得处理器个数 int* sumArray = new int[coreNum];//对应处理器个数,先生成一个数组 for (int i=0;i int k = omp_get_thread_num();//获得每个线程的ID sumArray[k] = sumArray[k]+a[i]; } for (int i = 0;i 用omp_get_num_procs()函数来获取处理器个数,用omp_get_thread_num()函数来获得每个线程的ID,为了使用这两个函数,我们需要include
#pragma omp parallel for reduction(+:sum)
存在的问题:
上面的代码虽然达到了目的,但是它产生了较多的额外操作。OpenMP提供了以下的方法解决线程竞争冒险。在#pragma omp parallel for后面加上了reduction(+:sum),它的意思是告诉编译器:下面的for循环你要分成多个线程跑,但每个线程都要保存变量sum的拷贝,循环结束后,所有线程把自己的sum累加起来作为最后的输出。
#includeint main(){ int sum = 0; int a[10] = {1,2,3,4,5,6,7,8,9,10}; #pragma omp parallel for reduction(+:sum) for (int i=0;i<10;i++) sum = sum + a[i]; std::cout<<"sum: "< 存在的问题:
#pragma omp critical
reduction虽然很方便,但它只支持一些基本操作,比如+,-,*,&,|,&&,||等。实例:求数组a的最大值,将结果保存在max里。
for循环还是被自动分成N份来并行执行,用#pragma omp critical将if(temp > max) max = temp 括了起来,它的意思是:各个线程还是并行执行for里面的语句,但当你们执行到critical里面时,要注意有没有其他线程正在里面执行,如果有的话,要等其他线程执行完再进去执行。这样就避免了race condition问题,但显而易见,它的执行速度会变低,因为可能存在线程等待情况。#includeint main(){ int max = 0; int a[10] = {11,2,33,49,113,20,321,250,689,16}; #pragma omp parallel for for (int i=0;i<10;i++) { int temp = a[i]; #pragma omp critical { if (temp > max) max = temp; } } std::cout<<"max: "< #pragma omp atomic 与critical类似,但比critical效率高
只支持x++ , x–, --x, ++x
x ( + - * / & | << >> ) = expr#includepragma omp single#include // OpenMP编程需要包含的头文件 int main() { int sum = 0; std::cout << "Before: " << sum << std::endl; #pragma omp parallel for for (int i = 0; i < 20000; ++i) { #pragma omp atomic sum++; } std::cout << "After: " << sum << std::endl; return 0; } 用在一段只被单个线程执行的代码段之前,表示后面的代码段将被单线程执行。
#pragma omp barrier
代码
结果
事件同步,各线程执行顺序是不同的,在并行结束的位置加barrier,可以在此位置让各线程同步
#pragma omp section
采用section定义的每段程序都将只执行一次,sections中的每段section将并行执行。一个程序中可以定义多个sections,每个sections中又可以定义多个section。同一个sections中section之间处于并行状态。sections与其他sections之间处于串行状态。
#include#include using namespace std; void SectionsTest() { int i; #pragma omp parallel num_threads(4) { #pragma omp sections //第1个sections { #pragma omp section { cout<<"section 1 线程ID:"< cout<<"section 2 线程ID:"< #pragma omp section { cout<<"section 3 线程ID:"< cout<<"section 4 线程ID:"< 运行程序,其结果有:
section 1 线程ID:0
section 2 线程ID:1
section 3 线程ID:3
section 4 线程ID:1section1 与section2并行, section3与section4并行
task
这两个section又串行
参考:https://blog.csdn.net/gengshenghong/article/details/7004594
task主要适用于不规则的循环迭代和递归的函数调用。都是无法使用for来完成的情况。#includevoid task(int p) { printf("task, Thread ID: %d, task: %dn", omp_get_thread_num(), p); } #define N 50 void init(int*a) { for(int i = 0;i < N;i++) a[i] = i + 1; } int main(int argc, _TCHAR* argv[]) { int a[N]; init(a); #pragma omp parallel num_threads(2) { #pragma omp single { for(int i = 0;i < N; i=i+a[i]) //i递增没有规律 { #pragma omp task task(a[i]); } } } return 0; } 用single表示只有一个线程会执行下面的代码,表示一开始程序单线程执行,遇到task之后动态的派发线程。OpenMP遇到了task之后,就会使用当前的线程或者延迟一会后接下来的线程来执行task定义的任务。task的作用,就是定义了一个显式的任务。
4、数据环境类导语
task和前面的for和sections的区别在于:
for/section 静态 task 动态
task是“动态”定义任务的,在运行过程中,只需要使用task就会定义一个任务,任务就会在一个线程上去执行,那么其它的任务就可以并行的执行。可能某一个任务执行了一半的时候,或者甚至要执行完的时候,程序可以去创建第二个任务,任务在一个线程上去执行,一个动态的过程,不像sections和for那样,在运行之前,已经可以判断出可以如何去分配任务。而且,task是可以进行嵌套定义的,可以用于递归的情况等等。OpenMP的数据处理子句包括private、firstprivate、lastprivate、shared、default、reduction copyin和copyprivate。它与编译制导指令parallel,for和sections相结合,用来控制变量的作用范围。
share没有采取保护会有数据竞争
private
shared子句声明的变量将在所有线程之间共享,所有线程都可以对同一个内存位置进行读写操作,程序员需确保多个线程正确地访问共享变量(通过critical等指令)
没有保护,因此两次运行结果不一样
private(list)每个线程都有它自己的私有变量副本,从申明的并行区域开始,为每个线程声明一个相同类型的新对象,对原始对象的所有引用都将替换为对新对象的引用,对于每个线程,声明为私有的变量未初始化
int i = 0; float a = 512.3; #pragma omp parallel private(i,a) { i = i + 1; printf("thread %d i = %d a= %fn", omp_get_thread_num(), i, a); } printf("out of parallel i = %d", i); }运行结果如下,每个线程都有一个独立的i变量,互不影响,同时也不影响原始的i。
firstprivate
firstprivate子句指定的变量不仅是private作用范围,同时在进入并行区域构造前根据其原始对象的值初始化
示例代码如下int i = 1; float a = 512.3; #pragma omp parallel firstprivate(i) private(a) { i = i + 1; printf("thread %d i = %d a= %fn", omp_get_thread_num(), i, a); } printf("out of parallel i = %d", i);运行结果如下,每个线程都有一个独立的i变量,且进入并行区域时每个线程的i都被初始化原始值1。
lastprivate
lastprivate子句指定的变量不仅是private作用范围,同时会将最后一次迭代或最后一个section执行的值复制回原来的变量
int i = 0; float a = 512.3; #pragma omp parallel { #pragma omp sections lastprivate(i) private(a) { #pragma omp section { i = i + omp_get_thread_num(); printf("thread %d i = %d a= %fn", omp_get_thread_num(), i, a); } #pragma omp section { i = i + omp_get_thread_num(); printf("thread %d i = %d a= %fn", omp_get_thread_num(), i, a); } } } printf("out of parallel i = %d", i);运行结果如下,每个线程都有一个独立的i变量,且退出并行区域时原始的i变量被设置成其中一个section线程的相应变量值。
参考:
https://blog.csdn.net/laobai1015/article/details/79020128?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162843230216780265414989%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=162843230216780265414989&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-79020128.first_rank_v2_pc_rank_v29&utm_term=openMP%E9%85%8D%E7%BD%AE&spm=1018.2226.3001.4187
https://zhuanlan.zhihu.com/p/61857547
https://blog.csdn.net/augusdi/article/details/8808158#
https://blog.csdn.net/u014357799/article/details/79387357
https://blog.csdn.net/rlyhaha/article/details/79420233



