通常使用Visual Studio2019来做为openmp的编程环境ios
调试-->属性-->C/C++-->全部选项-->Openmp支持改成 是(可使用下拉菜单)c++
严重性 代码 说明 项目 文件 行 禁止显示状态 禁止显示状态 错误 C2338 C++/CLI、C++/CX 或 OpenMP 不支持两阶段名称查找;请使用 /Zc:twoPhase- 多线程 C:\Users\tonyson_in_the_rain\source\repos\多线程\多线程\c1xx 1编程
若是报错,再在属性菜单中找到C/C++ --> 语言 -->符合模式下拉菜单中选择"否"windows
#include "stdio.h" #include "omp.h" #include "windows.h" int main() { printf("Hello from serial.\n"); printf("Thread number = %d\n", omp_get_thread_num()); //串行执行 Sleep(1000); #pragma omp parallel //开始并行执行 { printf("Hello from parallel. Thread number=%d\n", omp_get_thread_num()); Sleep(1000); } printf("Hello from serial again.\n"); return 0; }
运行结果以下:数组
开始是串行,主线程的号为0,以后的1 2 3为子线程安全
要求是for循环,并且必须能知道具体的循环次数.不可以使用break和return语句.数据结构
for循环的第一步是任务划分,若是有4个线程,100次循环,那么线程0就分配到了1-25次循环,而后线程1分配到26-50次,以此类推.多线程
int x[100], y[100], k, m; x[0] = 0; y[0] = 1; #pragma omp parallel for private(k) for (k = 1; k < 100; k++) { x[k] = y[k - 1] + 1; //S1 y[k] = x[k - 1] + 2; //S2 printf("x[%d]=%d thread=%d\n", k, x[k], omp_get_thread_num()); printf("y[%d]=%d thread=%d\n", k, y[k], omp_get_thread_num()); } printf("y=%d\n", y[99]); printf("x=%d\n", x[99]);
这样的话,若是分配好后4个线程并行,那么1号线程计算时,变量用的是前一次的结果,可是前一次操做尚未进行,变量尚未初始化直接就运行,程序会出错.ide
提供一个改写方法,这个方法不受线程数量的影响,最终只能划分为两份,由于划分以迭代次数为最小单位,而for循环最外层只循环两次,因此最多只能划分红两份.函数
#pragma omp parallel for private(m, k) for (m = 0; m < 2; m++) { for (k = m * 50 + 1; k < m * 50 + 50; k++) { x[k] = y[k - 1] + 1; //S1 y[k] = x[k - 1] + 2; //S2 printf("x[%d]=%d thread=%d\n", k, x[k], omp_get_thread_num()); printf("y[%d]=%d thread=%d\n", k, y[k], omp_get_thread_num()); } }
并非全部的for循环都会并行化,只有紧挨着编译指导语句pragma omp parallel for的for循环会并行化
int i;int j; #pragma omp parallel for private(j) //能够尝试去掉private语句,查看程序执行结果 for(i=0; i<2; i++) for(j=6; j<10; j++) printf( "i=%d j=%d\n", i , j); printf("######################\n"); for(i=0; i<2; i++) #pragma omp parallel for for(j=6; j<10; j++) printf( "i=%d j=%d\n", i , j );
上面部分运行结果:
其中一个线程得到了i=0时的任务,而另外一个得到了i=1的迭代任务,j是串行的
若是去掉private:
若是不不用private,那么j就变成了共享的变量,两个线程并行就会出现错误,而编译指导语句后面的for循环中的i变量默认是私有变量,因此能够正常执行.
下面部分的运行结果:
下面
会反复地把一个二元运算符应用在一个变量和另一个值上,好比数组求和
int main() { int arx[100], ary[100], n = 100, a = 0, b = 0; for (int i = 0; i < 100; i++) { arx[i] = 1; ary[i] = 1; } # pragma omp parallel for reduction(+:a,b)//能够去掉reduction子句,对比线程处理过程当中的不一样 for (int i = 0; i < n; i++) { a = a + arx[i]; b = b + ary[i]; printf("a=%d i= %d thread=%d\n", a, i, omp_get_thread_num()); printf("b=%d i= %d thread=%d\n", b, i, omp_get_thread_num()); } printf("a=%d b= %d thread=%d\n", a, b, omp_get_thread_num()); }
运算符 | 数据类型 | 默认初始值 |
---|---|---|
+ | 整数,浮点 | 0 |
***** | 整数,浮点 | 1 |
- | 整数,浮点 | 0 |
& | 整数 | 全部位都开启,****~0 |
| | 整数 | 0 |
^ | 整数 | 0 |
&& | 整数 | 1 |
|| | 整数 | 0 |
可使用的规约操做
#include "stdio.h" #include "omp.h" #include "windows.h" int main() { int val = 8; #pragma omp parallel for firstprivate(val) lastprivate(val) //此处可充分改变private语句,观察程序执行结果 for (int i = 0; i < 4; i++) //能够改变循环次数,获得不一样的最终值,如:i<7 { printf("i=%d val=%d thread=%d\n", i, val, omp_get_thread_num()); if (i == 2) val = 10000; if (i == 3) val = 11111; printf("i=%d val=%d thread=%d\n", i, val, omp_get_thread_num()); } printf("val=%d\n", val); }
最后迭代时i=3,val=11111,因此最后带回去11111便可.
int main() { #pragma omp parallel for (int i = 0; i < 5; i++) printf("hello world i=%d\n", i); printf("###########################\n"); #pragma omp parallel for for (int i = 0; i < 5; i++) printf("hello world i=%d\n", i); }
上面是普通的并行操做,下面是for循环的并行化,输出以下:
hello world i=0 hello world i=1 hello world i=2 hello world i=3 hello world i=4 hello world i=0 hello world i=1 hello world i=0 hello world i=1 hello world i=0 hello world i=1 hello world i=2 hello world i=3 hello world i=4 hello world i=2 hello world i=3 hello world i=4 hello world i=2 hello world i=3 hello world i=4 ########################### hello world i=0 hello world i=1 hello world i=4 hello world i=3 hello world i=2
上边的实际上就是重复了这个任务,4个线程重复执行相同的任务,而下面就是for循环的并行.
#include "stdio.h" #include "omp.h" #include "windows.h" int counter = 50; //using threadprivate #pragma omp threadprivate(counter) void inc_counter() { counter++; } int main() { #pragma omp parallel //注释上面的threadprivate子句,查看求和结果 { for (int i = 0; i < 10000; i++) inc_counter(); printf("counter=%d\n", counter); } }
正确的执行结果
counter=10050 counter=10050 counter=10050 counter=10050
若是注释掉#pragma omp threadprivate(counter)
说的就是一个普通的并行区域的编译指导语句
#pragma omp parallel
子句 private shared default reduction if copyin
并行区域编译指导语句的使用限制 程序块必须是只有单一入口和单一出口的程序块 不能从外面转入到程序块的内部,也不容许从程序块内部有多个出口转到程序块以外 程序块内部的跳转是容许的 程序块内部直接调用exit函数来退出整个程序的执行也是容许的
// OpenMP2.cpp : 定义控制台应用程序的入口点。 // #include "stdio.h" #include "omp.h" #include <windows.h> //使用Sleep()函数须要包含此头文件 int counter = 0; #pragma omp threadprivate(counter) void inc_counter() { counter++; } int main() { #pragma omp parallel //注释上面的threadprivate子句,查看求和结果 { for (int i = 0; i < 10000; i++) inc_counter(); printf("counter=%d\n", counter); } return 0; } /* counter=10000 counter=30162 counter=20000 counter=39535 */ /* counter=10000 counter=10000 counter=10000 counter=10000 */
copyin 能够把变量的值初始化到每一个子线程的副本里面
// OpenMP2.cpp : 定义控制台应用程序的入口点。 // #include "stdio.h" #include "omp.h" #include <windows.h> //使用Sleep()函数须要包含此头文件 int global; #pragma omp threadprivate(global) ///????????? int main() { global = 1000; #pragma omp parallel copyin(global) { printf("global=%d, thread=%d\n", global, omp_get_thread_num()); global = omp_get_thread_num(); printf("global=%d, thread=%d\n", global, omp_get_thread_num()); } printf("global=%d\n", global); printf("parallel again\n"); #pragma omp parallel printf("global=%d\n", global); return 0; }
为何是0呢?由于global是问的主线程的global,已经由主线程改为了0,而其余的线程中的global还保存着原来的值.
工做共享
工做队列 不断从队列中取出标识号来完成
根据线程号分配任务
//程序段12(OMP_NUM_THREADS=4) /* global=1000; #pragma omp parallel copyin(global) { printf("global=%d, thread=%d\n",global,omp_get_thread_num()); global=omp_get_thread_num(); printf("global=%d, thread=%d\n",global,omp_get_thread_num()); } printf("global=%d\n",global); printf("parallel again\n"); #pragma omp parallel printf("global=%d\n",global);*/ //使用copyin()子句的变量必须经过threadprivate()声明, //parallel后可使用private()子句、firstprivate()子句,不能使用lastprivate()子句 /*int g=100; #pragma omp parallel firstprivate(g) { printf("g=%d, thread=%d\n",g,omp_get_thread_num()); g=omp_get_thread_num(); printf("g=%d, thread=%d\n",g,omp_get_thread_num()); } printf("g=%d\n",g); printf("parallel again\n"); #pragma omp parallel printf("g=%d\n",g);*/
//程序段15 /*#pragma omp parallel { printf("outside loop thread=%d\n", omp_get_thread_num()); #pragma omp for for(int i=0;i<4;i++) printf("inside loop i=%d thread=%d\n", i, omp_get_thread_num()); } */
//程序段16 #pragma omp parallel sections { #pragma omp section printf("section 1 thread=%d\n",omp_get_thread_num()); #pragma omp section printf("section 2 thread=%d\n",omp_get_thread_num()); #pragma omp section printf("sectino 3 thread=%d\n",omp_get_thread_num()); } ``` ``` // OpenMP2.cpp : 定义控制台应用程序的入口点。 // #include "stdio.h" #include "omp.h" #include <windows.h> //使用Sleep()函数须要包含此头文件 int main() { #pragma omp parallel num_threads(4) { printf("parallel region before single. thread %d\n", omp_get_thread_num()); #pragma omp single //执行期间其余线程等待 { Sleep(1000); printf("single region by thread %d.\n", omp_get_thread_num()); } printf("parallel region after single. thread %d.\n", omp_get_thread_num()); } } ```  把single改为master,执行的结果仍是0,由于主线程就是0号线程,master只能由主线程执行 ## 并行区域的共享 / 2.根据线程号分配任务.因为每一个线程在执行的过程当中的线程标识号 // 是不一样的,能够根据这个线程标识号来分配不一样的任务 //#pragma omp parallel private(myid) // { // int nthreads = omp_get_num_threads(); // int myid = omp_get_thread_num(); // work_done(myid, nthreads); // 分配任务函数 // } ### 使用for语句分配任务 ``` int main() { #pragma omp parallel num_threads(2) { printf("outside loop thread=%d\n", omp_get_thread_num()); #pragma omp for for (int i = 0; i < 4; i++) printf("inside loop i=%d thread=%d\n", i, omp_get_thread_num()); } } outside loop thread=0 outside loop thread=1 inside loop i=2 thread=1 inside loop i=3 thread=1 inside loop i=0 thread=0 inside loop i=1 thread=0 int main() { #pragma omp parallel num_threads(4) { printf("outside loop thread=%d\n", omp_get_thread_num()); #pragma omp for for (int i = 0; i < 4; i++) printf("inside loop i=%d thread=%d\n", i, omp_get_thread_num()); } } outside loop thread=0 inside loop i=0 thread=0 outside loop thread=2 inside loop i=2 thread=2 outside loop thread=1 inside loop i=1 thread=1 outside loop thread=3 inside loop i=3 thread=3 ``` ### 使用工做分区 ``` // OpenMP2.cpp : 定义控制台应用程序的入口点。 // #include "stdio.h" #include "omp.h" #include <windows.h> //使用Sleep()函数须要包含此头文件 int global = 88; #pragma omp threadprivate(global) int counter = 50; //using threadprivate #pragma omp threadprivate(counter) void inc_counter() { counter++; } int main() { #pragma omp parallel sections { #pragma omp section printf("section 1 thread=%d\n", omp_get_thread_num()); #pragma omp section printf("section 2 thread=%d\n", omp_get_thread_num()); #pragma omp section printf("sectino 3 thread=%d\n", omp_get_thread_num()); } } ```  ## openmp线程同步 提供了三种不一样的互斥锁机制,分别是临界区,原子操做和库函数 原子操做只能做用在语言内建的基本数据结构 也能够加锁,比较安全 ``` omp_lock_t lock; omp_init_lock(&lock); omp_destroy_lock(&lock); omp_set_lock(&lock); omp_unset_lock(&lock); ``` ## 隐含的同步屏障 默认是把1-9分给了4个线程,执行完i的循环以后才能够输出finished,使用nowait后能够直接输出finished ``` int main() { #pragma omp parallel { #pragma omp for nowait for (int i = 0; i < 9; i++) printf("i=%d thread=%d\n", i, omp_get_thread_num()); printf("finished\n"); } } ``` ``` i=0 thread=0 i=1 thread=0 i=2 thread=0 finished i=5 thread=2 i=6 thread=2 finished i=7 thread=3 i=3 thread=1 i=8 thread=3 i=4 thread=1 finished finished ``` 若是去掉nowait ``` i=0 thread=0 i=1 thread=0 i=2 thread=0 i=3 thread=1 i=7 thread=3 i=8 thread=3 i=5 thread=2 i=6 thread=2 i=4 thread=1 finished finished finished finished ``` 能够控制每一个子任务之间的并行部分和串行部分,能够先执行并行最后串行. ``` // OpenMP2.cpp : 定义控制台应用程序的入口点。 // #include "stdio.h" #include "omp.h" #include <windows.h> //使用Sleep()函数须要包含此头文件 void work(int k) { printf("并行--thread id =%d k=%d\n", omp_get_thread_num(), k); #pragma omp ordered printf("order-id=%d k=%d\n", omp_get_thread_num(), k); } void ordered_func(int lb, int ub, int stride) { int i; #pragma omp parallel for ordered schedule(dynamic) num_threads(5) for (i = lb; i < ub; i += stride) work(i); } int main() { ordered_func(0, 50, 5); } ```  并行执行的时候顺序 后面的须要等待,因此就排在后面去了 ## if子句的应用 若是if成立,那么就并行执行,不然就串行执行 [TOC] ## 火车卖票 ```c++ // OpenMP2.cpp : 定义控制台应用程序的入口点。 // #include "stdio.h" #include "omp.h" #include <windows.h> //使用Sleep()函数须要包含此头文件 int num; omp_lock_t lock; int getnum() { int temp = num; //omp_set_nest_lock(&lock); #pragma omp atomic num--; //omp_unset_nest_lock(&lock); return num+1; } void chushou(int i) { int s = getnum(); while (s >= 0) { omp_set_lock(&lock); printf("站点%d卖掉了第%d张票\n", i, s); s = getnum(); omp_unset_lock(&lock); Sleep(500); } } int main() { num = 100; int myid; omp_init_lock(&lock); #pragma omp parallel private(myid) num_threads(4) { myid = omp_get_thread_num(); //printf("my id is:%d\n", myid); chushou(myid); } omp_destroy_lock(&lock); return 0; } ``` ## 生产消费循环队列 ```c++ #include "stdio.h" #include "omp.h" #include <windows.h> //使用Sleep()函数须要包含此头文件 int buf[5];//缓冲区的大小 int poi; int poi2; int num; omp_lock_t lock; void shengchan() { puts("shengchan"); while (true) { omp_set_lock(&lock); if (num < 5) { while (buf[poi] == 1)poi = (poi + 1) % 5; printf("生产者在%d位置上放置了一个\n", poi); buf[poi] = 1; num++; poi = (poi + 1) % 5; } omp_unset_lock(&lock); Sleep(500); } } void xiaofei() { puts("xiaofei"); while (true) { omp_set_lock(&lock); //printf("%d\n", num); if (num>=1) { while (buf[poi2] == 0)poi2 = (poi2 + 1) % 5; printf("消费者在%d位置上消费了一个\n", poi2); buf[poi2] = 0; num--; } omp_unset_lock(&lock); Sleep(500); } } int main() { omp_init_lock(&lock); #pragma omp parallel sections num_threads(2) { #pragma omp section shengchan(); #pragma omp section xiaofei(); } omp_destroy_lock(&lock); return 0; } ``` ## 蒙特卡洛圆周率 ```c++ #include "stdio.h" #include "omp.h" #include <windows.h> //使用Sleep()函数须要包含此头文件 #include<time.h> #include<iostream> using namespace std; double distance(double x, double y) { return sqrt((x - 0.5) * (x - 0.5) + (y - 0.5) * (y - 0.5)); } bool judge(double x,double y) { return distance(x, y) <= 0.5; } int in_num; int main() { /* for (int i = 1; i <= 5; i++) { cout << rand() / (double)RAND_MAX << endl; }*/ bool flag = false; double x; double y; #pragma omp for private(flag,x,y) for (int i = 1; i <= 10000; i++) { x = rand() / (double)RAND_MAX; y = rand() / (double)RAND_MAX; flag = judge(x,y); if (flag) { #pragma omp atomic in_num++; } } double ans = (double)in_num / 10000; cout << ans*4 << endl; } ``` ## 多线程二维数组和解法1 firstprivate+atomic ```c++ #include "stdio.h" #include "omp.h" #include <windows.h> //使用Sleep()函数须要包含此头文件 #include<time.h> #include<iostream> using namespace std; int a[5][5] = { {1,1,1,1,1},{2,2,2,2,2},{3,3,3,3,3},{4,4,4,4,4},{5,5,5,5,5} }; int final_ans = 0; void increase(int temp_sum) { #pragma omp atomic final_ans += temp_sum; } int main() { int temp_sum=0; int i,j; #pragma omp parallel for private(i,j) firstprivate(temp_sum) num_threads(5)//每一个线程必须一致,或者采用ppt上的例子进行划分 // firstprivate(temp_sum) reduction(+:temp_sum) 这两个不能同时出现 for (i = 0; i <= 4; i++) { //temp_sum += 1; //printf("%d 当前的temp_sum值为%d\n",i, temp_sum); for (j = 0; j <= 4; j++) { temp_sum += a[i][j]; } printf("temp_sum is %d\n", temp_sum); increase(temp_sum); } printf("%d\n", final_ans); return 0; } ``` ## 多线程二维数组解法2 线程能够不用对应数量 ``` #include "stdio.h" #include "omp.h" #include <windows.h> //使用Sleep()函数须要包含此头文件 #include<time.h> #include<iostream> using namespace std; int a[5][5] = { {1,1,1,1,1},{2,2,2,2,2},{3,3,3,3,3},{4,4,4,4,4},{5,5,5,5,5} }; int ans_buf[5]; int main() { int i, j; #pragma omp parallel for num_threads(3) private(j) for (int i = 0; i <= 4; i++) { for (int j = 0; j <= 4; j++) { ans_buf[i] += a[i][j]; } } int sum = 0; for (int i = 0; i <= 4; i++) sum += ans_buf[i]; printf("%d\n", sum); } ``` 1.模拟龟兔赛跑,先到达终点者输出 2.多线程二维矩阵前缀和(难) 须要先了解二维前缀和 3.模拟多我的经过一个山洞的模拟,这个山洞每次只能经过一我的,每一个人经过山洞的时间为5秒,随机生成10我的,同时准备过此山洞,显示如下每次经过山洞的人的姓名。 4.多线程斐波那契数列(有点难) 5.openmp 快排 归并排序 6.3节点有5我的要去0 0节点有5我的要去3 防死锁  7.多线程 大数求和 lastprivate求和 并行串行判断