ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

openMP并行编程基础

2020-11-26 21:58:17  阅读:236  来源: 互联网

标签:num get int 编程 并行 omp pragma printf openMP


OpenMP基础结构:

例程:

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

int main(int argc, char *argv[]){

	int nt, tid;
	int np;
	const int MAX=256;
	char buf[32]={0};

#pragma omp parallel private(nt, tid) num_threads(8)
	{
		tid=114514;
		tid=omp_get_thread_num ();
		printf("Hello world from OpenMP thread %d\n",tid);
		if(tid==0){
			nt=omp_get_num_threads ();
			printf("num of threads = %d\n",nt);
		}
	}


	return 0;
}

  • 需要在编译的时候添加-fopenmp参数:

    image-20201122115952237

    对于Qt, 需要在pro配置文件中添加:

    image-20201122120022467

  • 在编程时, 需要在并行域处使用#pragma制导, 并将后头的程序全部放入代码块内

    clauses用来说明并行域的附加信息

    image-20201122120201331
  • 在并行域结束后会有一个自动的barrier隐式同步

常用の附加信息:

并行块:

parallel

表示下头的程序所有线程都会执行

设置数据为公有或私有:

shared(var1, var2, ...)
    
private(var1, var2, ...)    

设置变量为线程私有或是公共变量

前者就和串行执行时相同, 后者所有线程访问此变量时, 使用的都是同一地址, 在编程时需要注意数据竞争问题

default(shared|none)

在fortran中还可以指定为private

设置所有未声明的变量为指定模式, 为none时需指定所有变量的模式, 否则会报错

OpenMP中, 有几个默认情况:

  • 所有的循环变量(如for), 临时变量, 写变量 通常为私有
  • 数组变量, 只读变量 通常为共有
  • 默认情况为公有

拷贝:

copyin

用于将主进程中threadprivate的数据拷贝到每个线程中, 使得每个线程都获得初始值相同的私有变量

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

int a=114514;
#pragma omp threadprivate(a)

int main(int argc, char *argv[]){

	int nt, tid;

	int np;
	const int MAX=256;
	char buf[32]={0};

#pragma omp parallel private(nt, tid) shared(np) num_threads(4) copyin(a)
	{
		tid=114514;
		tid=omp_get_thread_num ();
		printf("Thread %d: a= %d\n",tid ,a );
	}
	return 0;
}

image-20201122125916835

归约:

reduction(+:pi)

与MPI的MPI_Reduce相似, 会在线程join时执行归约操作:

他有俩参数

  1. 对数据实行的操作, 一定是一个二元运算, 包括min & max
  2. 指定的变量
    reduction中指定的变量无需进行额外的shared() 或 private() 设置
    其会被自动分割为private和shared

例程:

使用Reduction计算PI

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

static int n=10000;
const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double pi, startTime=0.0, endTime=0.0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();

#pragma omp parallel reduction(+:pi)
	{
		int tid=omp_get_thread_num ();

		int rowTime=n/NUM_THREADS;
		double temp=0.0;
		for(int i=0;i<rowTime;++i){
			temp=((i*NUM_THREADS+tid)+0.5)/n;
			pi+=4.0/(1+temp*temp);
		}
		pi/=n;
		printf("Thread %d : data=%lf\n", tid, pi);

	}

	printf("PI = %.14lf\n",pi);
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

任务划分并行制导:

for制导:

相当于OpenMP自动将for划分到各个线程中执行, 无需手动分割任务

#pragma omp for

通常直接放到需要分割的for上头, 嵌套与parallel块中

每次for制导完成后所有线程都会执行一次同步

此时需要注意数据竞争问题

例程:

使用for制导 + reduction归约求PI

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

static int n=100000;

const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double pi, startTime=0.0, endTime=0.0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();

#pragma omp parallel reduction(+:pi)
	{
		int tid=omp_get_thread_num ();
		double temp=0.0;
#pragma omp for
		for(int i=0;i<n;++i){
			temp=(i+0.5)/n;
			pi+=4.0/(1+temp*temp);
		}
		pi/=n;
		printf("Thread %d : data=%lf\n", tid, pi);
	}

	printf("PI = %.14lf\n",pi);
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

image-20201122142040883


指定for制导的数据分组

默认情况下, OpenMP会将数据分成差不多这个样子:

image-20201122143434647

如果要分成这个样子(即每个线程每次拿到固定大小的数据块), 需要使用schedule()操作:

image-20201122143504229

schedule(mode [, chunkSize])
  • 第一个为必要参数, 指定schedule的模式:

    可选: static, dynamic, duided, runtime

    最后一个runtime 不是一个模式, 而是使用环境变量OMP_SCHEDULED来选择三种中的某一个, 并且此时如果指定chunkSize是非法的

  • 第二个为数据块的大小, int类型

不同的调度模式:

最标准的静态分配, 与手动分配相同

image-20201122144018733

这玩意相当于各个线程负载均衡, 每次申请的数量仍然是chunkSize, 但执行较快的进程申请的次数也较多, 一定程度上做到了线程负载均衡

image-20201122144115452

进阶的dynamic模式, 如下:

image-20201122144125511

image-20201122144642649

sections制导:

分为两种:

#pragma onp sections

#pragma omp parallel sections

第一种为串行代码, 由主进程进行

第二种为并行代码, 每个进程执行其中不同的代码块, 相当于MPMD

例程:

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

static int n=100000;

const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double startTime=0.0, endTime=0.0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();
	//----------------------------------------

#pragma omp sections
	{
#pragma omp section
		{
			printf("Thread %d : section 1\n",omp_get_thread_num ());
		}
#pragma omp section
		{
			printf("Thread %d : section 2\n",omp_get_thread_num ());
		}
#pragma omp section
		{
			printf("Thread %d : section 3\n",omp_get_thread_num ());
		}
	}

#pragma omp parallel sections
	{
#pragma omp section
		{
			printf("Thread %d : section 4\n",omp_get_thread_num ());
		}
#pragma omp section
		{
			printf("Thread %d : section 5\n",omp_get_thread_num ());
		}
#pragma omp section
		{
			printf("Thread %d : section 6\n",omp_get_thread_num ());
		}
	}
	//----------------------------------------
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

image-20201122172017096

single制导:

single块内的代码仅由最先到达的进程执行(仅被执行一次), 其他进程会在single块结束处等待所有进程同步

例程:

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double startTime=0.0, endTime=0.0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();
	//----------------------------------------
#pragma omp parallel
	{
#pragma omp single
		{
			printf("Thread %d : single 1\n",omp_get_thread_num ());
		}
#pragma omp single
		{
			printf("Thread %d : single 2\n",omp_get_thread_num ());
		}
#pragma omp single
		{
			printf("Thread %d : single 3\n",omp_get_thread_num ());
		}
#pragma omp single
		{
			printf("Thread %d : single 4\n",omp_get_thread_num ());
		}
#pragma omp single
		{
			printf("Thread %d : single 5\n",omp_get_thread_num ());
		}
	}


	//----------------------------------------
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

image-20201122172939411

使用nowait可以取消进程同步, 这玩意也可以用在所有含有隐式同步的制导中, 用于取消隐式同步

使用nowait后, 就变成了这样:

image-20201122193612117

master制导

master块内的代码仅由主进程执行

例程

还是上头的程序

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double startTime=0.0, endTime=0.0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();
	//----------------------------------------
#pragma omp parallel
	{
#pragma omp master
		{
			printf("Thread %d : master 1\n",omp_get_thread_num ());
		}
#pragma omp master
		{
			printf("Thread %d : master 2\n",omp_get_thread_num ());
		}
#pragma omp master
		{
			printf("Thread %d : master 3\n",omp_get_thread_num ());
		}
#pragma omp master
		{
			printf("Thread %d : master 4\n",omp_get_thread_num ());
		}
#pragma omp master
		{
			printf("Thread %d : master 5\n",omp_get_thread_num ());
		}
	}


	//----------------------------------------
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

image-20201122193903035

barrier制导

与MPI_Barrier()相似, 此语句强制所有进程在此处同步, 只有所有进程同步完成后才能进行下头的步骤

例程

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double startTime=0.0, endTime=0.0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();
	//----------------------------------------
#pragma omp parallel
	{
#pragma omp single nowait
		{
			printf("Thread %d : single 1\n",omp_get_thread_num ());
		}
#pragma omp barrier
#pragma omp single nowait
		{
			printf("Thread %d : single 2\n",omp_get_thread_num ());
		}
#pragma omp barrier
#pragma omp single nowait
		{
			printf("Thread %d : single 3\n",omp_get_thread_num ());
		}
#pragma omp barrier
#pragma omp single nowait
		{
			printf("Thread %d : single 4\n",omp_get_thread_num ());
		}
#pragma omp barrier
#pragma omp single nowait
		{
			printf("Thread %d : single 5\n",omp_get_thread_num ());
		}
#pragma omp barrier
	}


	//----------------------------------------
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

image-20201122194105613

flush制导:

#pragma omp flush [(var1, var2, ...)]

flush标记了一个变量同步点, 保证所有进程中的共享变量值相同

强制刷新每个线程的临时视图,使其和内存视图保持一致,即:使线程中缓存的变量值和内存中的变量值保持一致

通常情况下很少使用到flush, 因为其会在大部分#pragma语句中隐式执行

锁:

多线程并发执行必然需要锁来保证安全性

critical锁:

critical译为临界, 其实就是将代码块标记为临界区, 同一时间只能有一个进程在临界区内

#pragma omp critical [(name)]

{

   <临界区代码>

}

后头跟的name参数是临界段的名称, 通常还是推荐加个名字

例程:

每次运行这个程序, 结果都是不一样的

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

const int MAX=10000;
const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double startTime=0.0, endTime=0.0;
	int sum=0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();
	//----------------------------------------
#pragma omp parallel shared(sum)
	{
#pragma omp for
		for(int i=0;i<MAX;++i){
			sum+=i;
		}
	}
	printf("sum= %d\n",sum);

	//----------------------------------------
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

image-20201122195556746

image-20201122195600523

加上critical之后:

#pragma omp parallel shared(sum)
{
    #pragma omp for
    for(int i=0;i<MAX;++i){
        #pragma omp critical
        sum+=i;
    }
}
printf("sum= %d\n",sum);

image-20201122195730656

atomic锁:

atomic操作与critical操作作用相似, 但是执行的速度快很多

同一个操作, critical & atomic 的比较:

image-20201122201207781

image-20201122201153610

使用上, atomic仅支持部分语句, 且不支持程序块, 但是critical支持程序块:

image-20201122201317155

所以原则上, 对于数据的更新, 优先使用atomic, 而后在考虑critical

互斥锁:

这部分PPT讲的不是很详细

如需详细学习可以参考这里:

https://www.cnblogs.com/xudong-bupt/p/3574818.html

  • void omp_init_lock(omp_lock *) 初始化互斥器
  • void omp_destroy_lock(omp_lock *) 销毁互斥器
  • void omp_set_lock(omp_lock *) 获得互斥器
  • void omp_unset_lock(omp_lock *) 释放互斥器
  • bool omp_test_lock(omp_lock *) 试图获得互斥器,如果获得成功返回true,否则返回false

这里有点像PV操作

运行库函数:

这里主要介绍常用的OpenMP库函数

其实在之前的例程中都已经用过了

  1. 获取线程数:

    int omp_get_num_threads();
    
  2. 获取线程号:

    int omp_get_thread_num();
    
  3. 设定执行的线程数量:

    void omp_set_num_threads();
    
  4. 获取墙上时间:

    double omp_get_wtime();
    

    这个和MPI相似

环境变量:

本部分就这些:

image-20201122205522960

image-20201122205530959

标签:num,get,int,编程,并行,omp,pragma,printf,openMP
来源: https://blog.csdn.net/qq_42683011/article/details/110206079

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有