通常使用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()); } } ``` ![](https://cdn.mathpix.com/snip/images/zXXUHQVQTcvLppTj4DQTfyhmsOki36df92Na-Apgp1c.original.fullsize.png) 把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()); } } ``` ![](https://cdn.mathpix.com/snip/images/_6-xN-thZMv2sVisMgUOpmTDP5VFyBHORPYDhtcBxgg.original.fullsize.png) ## 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); } ``` ![](https://cdn.mathpix.com/snip/images/e8isf-nDopr1tSsVmI1yDFGk9_KsXVQXEen5ye4eIx4.original.fullsize.png) 並行執行的時候順序 後面的須要等待,因此就排在後面去了 ## 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 防死鎖 ![](https://cdn.mathpix.com/snip/images/Q6wKBNepMdElqzyvI_UPG1xh-ESfd5cZZvo6CQd7M7Q.original.fullsize.png) 7.多線程 大數求和 lastprivate求和 並行串行判斷