環境配置
一般使用Visual Studio2019來作為openmp的編程環境
調試-->屬性-->C/C++-->所有選項-->Openmp支持改為 是(可以使用下拉菜單)
嚴重性 代碼 說明 項目 文件 行 禁止顯示狀態 禁止顯示狀態
錯誤 C2338 C++/CLI、C++/CX 或 OpenMP 不支持兩階段名稱查找;請使用 /Zc:twoPhase- 多線程 C:\Users\tonyson_in_the_rain\source\repos\多線程\多線程\c1xx 1
如果報錯,再在屬性菜單中找到C/C++ --> 語言 -->符合模式下拉菜單中選擇"否"
第一個程序
- omp_get_thread_num()返回線程的編號
-
pragma omp parallel 用作注釋的形式,即使沒有openmp功能的編譯環境也能夠串行地正常執行程序.
#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號線程計算時,變量用的是前一次的結果,但是前一次操作還沒有進行,變量還沒有初始化直接就運行,程序會出錯.
提供一個改寫方法,這個方法不受線程數量的影響,最終只能划分為兩份,因為划分以迭代次數為最小單位,而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 |
可以使用的規約操作
私有變量的初始化和終結
- firstprivate把變量初始的值的帶進來,取自原來同名變量的值
- lastprivate把變量的值帶回去(將最后一次循環的相應變量賦給val
#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循環的並行.
私有全局變量
- threadprivate 每個線程有一個私有的副本,相互不要干擾
#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求和
並行串行判斷