Openmp多线程编程练习

环境配置

通常使用Visual Studio2019来做为openmp的编程环境ios

调试-->属性-->C/C++-->全部选项-->Openmp支持改成 是(可使用下拉菜单)c++

23

严重性 代码 说明 项目 文件 行 禁止显示状态 禁止显示状态 错误 C2338 C++/CLI、C++/CX 或 OpenMP 不支持两阶段名称查找;请使用 /Zc:twoPhase- 多线程 C:\Users\tonyson_in_the_rain\source\repos\多线程\多线程\c1xx 1编程

若是报错,再在属性菜单中找到C/C++ --> 语言 -->符合模式下拉菜单中选择"否"windows

}MCU$(Y)V@6SJLM@9}H7BP6

第一个程序

  • 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号线程计算时,变量用的是前一次的结果,可是前一次操做尚未进行,变量尚未初始化直接就运行,程序会出错.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

可使用的规约操做

私有变量的初始化和终结

  • 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());
	}
}
```

![](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求和

并行串行判断