openMP简介

openMP是一个编译器指令和库函数的集合,主要是为共享式存储计算机上的并行程序设计使用的。

  • 当计算机升级到多核时,程序中创建的线程数量需要随CPU核数变化,如在CPU核数超过线程数量的机器上运行,则不能很好的完全利用机器性能,虽然通过可以通过操作系统的API创建可变化数量的线程,但是比较麻烦,不如openMP方便
  • 操作系统API创建线程时,需要线程函数入口,如pthread编程。对于同一函数或者同一循环内的并行非常不利,函数入口非常之多,而openMP不需要函数入口。
  • 现在主流的操作系统的API 互不兼容,移植性非常差。openMP是标准规范,支持它的编译器都执行同一套标准,很好的解决了这个问题。

openMP的库函数和指令

指令的格式: #pragma omp 指令 [字句]

常见库函数:

指令 含义 库函数 含义
parallel 所表示的代码将被多个线程并行执行 omp_set_num_threads(parameter) 设置线程数目
parallel for 对for循环进行拆分并行 omp_get_threads_num() 返回线程号
barrier 执行到barrier时,等待所有线程执行完,再往下执行 omp_get_num_threads() 返回并行区域中的活动线程个数
master / single 指定代码块由主线程/随机一个单线程执行 omp_get_num_procs() 返回运行本线程的处理器个数
parallel sections section语句用在sections语句里面,将sections语句里的代码分成几个不同的段,每段都并行执行 omp_init_lock (parameter) 初始化一个简单的锁
ordered 指定并行区域的循环按顺序执行 omp_set_lock(parameter) 上锁
critical 用在代码临界区之前,让线程互斥的访问资源 omp_unset_lock(parameter) 解锁
omp_destroy_lock(parameter) 关闭一个锁

openMP指令和库函数的用法示例

parallel :

#include "omp.h"    //openmp的头文件
#include "stdio.h"
#define NUM_THREADS 4
int main()
{
int i ;
omp_set_num_threads(NUM_THREADS) ; //设置线程的个数
#pragma omp parallel
{
// 被parallel所表示的这个区域叫做并行块,每个线程都会执行这个块中的所有代码
printf ("hello world! \n");
for (i=0;i<5;i++)
printf("i=%d,thread = %d\n",i,omp_get_thread_num());
}
}
hello world!
i=0,thread = 0
i=1,thread = 0
i=2,thread = 0
i=3,thread = 0
i=4,thread = 0
hello world!
i=0,thread = 3
i=1,thread = 3
i=2,thread = 3
i=3,thread = 3
i=4,thread = 3
hello world!
i=0,thread = 1
i=1,thread = 1
i=2,thread = 1
i=3,thread = 1
i=4,thread = 1
hello world!
i=0,thread = 2
i=1,thread = 2
i=2,thread = 2
i=3,thread = 2
i=4,thread = 2

parallel for :

牵扯到for循环时,往往需要用到parallel for指令。

#include "omp.h"
#include "stdio.h"
#define NUM_THREADS 3
int main()
{
int i,j,k ;
omp_set_num_threads(NUM_THREADS);
#pragma omp parallel for
//此并行块中的for循环,把for循环体中的代码并行执行,即整个for循环被拆分为多个线程执行
//注意,parallel是连for循环一起并行
for (i = 0;i<5;i++)
printf("i= %d,thread=%d\n",i,omp_get_thread_num());
for (j=0;j<4;j++) //普通循环,仅一个线程
printf("j= %d,thread=%d\n",j,omp_get_thread_num());
return 0;
}
i= 0,thread=0
i= 1,thread=0
i= 4,thread=2
i= 2,thread=1
i= 3,thread=1
j= 0,thread=0
j= 1,thread=0
j= 2,thread=0
j= 3,thread=0

这种写法很有局限,就是#pragma omp parallel for 只能作用到紧跟着的for循环,也就是说,并行块中第一句话只能是for循环,不能是其他代码。因为这个写法为for循环专属。可以将上述写成如下形式:

#include "omp.h"
#include "stdio.h"
#define NUM_THREADS 3
int main()
{
int i,j,k ;
omp_set_num_threads(NUM_THREADS);
#pragma omp parallel
{
printf("HelloWorld! , thread=%d\n",omp_get_thread_num()); //每个线程都执行这条语句
#pragma omp for
//这个并行块中的代码,对for循环体中的代码进行并行执行
for (i = 0;i<5;i++){
printf("i= %d,thread=%d\n",i,omp_get_thread_num());
}
#pragma omp for
//这个并行块中的代码,对for循环体中的代码进行并行执行
for (j=0;j<4;j++){
printf("j= %d,thread=%d\n",j,omp_get_thread_num());
}
}
return 0;
}
HelloWorld! , thread=0
i= 0,thread=0
i= 1,thread=0
HelloWorld! , thread=2
i= 4,thread=2
HelloWorld! , thread=1
i= 2,thread=1
i= 3,thread=1
j= 0,thread=0
j= 1,thread=0
j= 2,thread=1
j= 3,thread=1

可见,第二种写法完全能够完成对for循环的拆分并行,而且能够多次对多个for循环进行操作,更好的是,这种写法衍生了另一种功能,就是能够输出helleworld的那条输出语句,这条语句能够被所有的线程执行,如果for循环需要为每个线程赋值一个变量,那么这个变量可以放在此输出语句的位置,示例请看文章最后的例子。

barrier:

#include <stdio.h>
#include "omp.h"
int main (){
int i,j ;
omp_set_num_threads (5);
#pragma omp parallel
{
printf ("hello world!,thread=%d\n", omp_get_thread_num ());
#pragma omp barrier //执行到此代码时,程序暂停,直到上一条输出语句被所有线程都执行完后,才开始执行下面的语句。
#pragma omp for
for ( i = 0; i < 5; i++)
printf ("i= %d,thread=%d\n",i, omp_get_thread_num ());
#pragma omp barrier //执行到此代码时,程序暂停,直到上一条的for循环语句被所有线程都并行执行完后,才开始执行下面的语句。
#pragma omp for
for ( j = 0; j < 5; j++)
printf ("j= %d ,thread= %d\n", j,omp_get_thread_num ());
}
}
hello world!,thread=4
hello world!,thread=1
hello world!,thread=3
hello world!,thread=2
hello world!,thread=0
i= 4,thread=4
i= 0,thread=0
i= 3,thread=3
i= 1,thread=1
i= 2,thread=2
j= 0 ,thread= 0
j= 1 ,thread= 1
j= 2 ,thread= 2
j= 4 ,thread= 4
j= 3 ,thread= 3

master / single :

看了对于for循环的并行之后,产生了一个新的问题,如果要在两个并行的for循环之间插入一个单线程执行的语句,应该如下做:

#include "omp.h"
#include "stdio.h"
#define NUM_THREADS 5
int main()
{
int i ,j ;
omp_set_num_threads(NUM_THREADS) ;
#pragma omp parallel for
for (i=0;i<4;i++)
printf ("i = %d ,thread=%d \n",i,omp_get_thread_num());
//以下输出语句位于两个for循环之间的代码,只能由一个线程来执行
printf ("I am a single thread %d \n",omp_get_thread_num());
#pragma omp parallel for
for (j=0;j<4;j++)
printf ("j = %d ,thread=%d \n",j,omp_get_thread_num()); return 0;
}
i = 0 ,thread=0
i = 3 ,thread=3
i = 2 ,thread=2
i = 1 ,thread=1
I am a single thread 0
j = 3 ,thread=3
j = 1 ,thread=1
j = 0 ,thread=0
j = 2 ,thread=2

但是上述的程序看起来很麻烦,master和single指令就是解决这个问题的:

#include <stdio.h>
#include "omp.h"
#define NUM_THREADS 5
int main (){
int i ,j;
omp_set_num_threads (NUM_THREADS);
#pragma omp parallel
{
#pragma omp for
for (i = 0; i < 4; i++)
printf ("i= %d, thread= %d\n",i, omp_get_thread_num ());
#pragma omp barrier
// #pragma omp master //下面的程序由主线程执行
#pragma omp single //下面的程序由随便一个单线程执行
printf ("I am a single thread ! thread= %d\n", omp_get_thread_num ());
#pragma omp barrier
#pragma omp for
for (j = 0; j < 5; j++)
printf ("j= %d, thread= %d\n",j, omp_get_thread_num ());
}
}
i= 2, thread= 2
i= 0, thread= 0
i= 1, thread= 1
i= 3, thread= 3
I am a single thread ! thread= 2
j= 2, thread= 2
j= 0, thread= 0
j= 3, thread= 3
j= 1, thread= 1
j= 4, thread= 4

效果是一样的,master 是指定用主线程0,而single是随机的一个单线程执行

parallel sections:

#include <stdio.h>
#include "omp.h"
#define NUM_THREADS 10
int main () {
omp_set_num_threads (NUM_THREADS);
#pragma omp parallel sections
{
#pragma omp section //并行执行
printf ("thread %d section A!\n", omp_get_thread_num ());
#pragma omp section //并行执行
printf ("thread %d section B!\n", omp_get_thread_num ());
#pragma omp section //并行执行
printf ("thread %d section C!\n", omp_get_thread_num ());
#pragma omp section //并行执行
printf ("thread %d section D!\n", omp_get_thread_num ());
#pragma omp section //并行执行
printf ("thread %d section E!\n", omp_get_thread_num ()); }
}
thread 4 section A!
thread 4 section E!
thread 8 section D!
thread 3 section C!
thread 0 section B!

parallel for 相似,可以写成如下形式:

#include <stdio.h>
#include "omp.h"
#define NUM_THREADS 3
int main () {
omp_set_num_threads (NUM_THREADS);
#pragma omp parallel
{
#pragma omp sections
{
#pragma omp section
printf ("thread %d section A!\n", omp_get_thread_num ());
#pragma omp section
printf ("thread %d section B!\n", omp_get_thread_num ());
}
#pragma omp sections
{
#pragma omp section
printf ("thread %d section C!\n", omp_get_thread_num ());
#pragma omp section
printf ("thread %d section D!\n", omp_get_thread_num ());
#pragma omp section
printf ("thread %d section E!\n", omp_get_thread_num ());
}
}
}

ordered:

#include <stdio.h>
#include <omp.h>
main ()
{
int i ;
omp_set_num_threads(5) ;
#pragma omp parallel for ordered
for ( i = 1; i <= 5; i++)
{
#pragma omp ordered //指定以下的循环体按照顺序执行
printf ("i=%d,thread=%d\n", i,omp_get_thread_num());
}
}
i=1,thread=0
i=2,thread=1
i=3,thread=2
i=4,thread=3
i=5,thread=4

openMP中的互斥(锁)

critical:

这个指令可以有枷锁的效果,所指定的代码表示只允许一个线程进行操作

/*
*加和程序,从1一直加到100的和
*
* */
#include <stdio.h>
#include "omp.h"
int main(){
int sum=0;
#pragma omp parallel
{
int i=0;
int id=omp_get_thread_num(); //获得当前并行区域中活动线程个数
int nthread=omp_get_num_threads(); //返回当前的线程号
for(i=id+1;i<=100;i+=nthread)
#pragma omp critical //对sum进行互斥的操作,同一时间,只允许一个线程对sum变量进行操作
sum=sum+i;
}
printf("sum=%d\n",sum);
}
sum=5050

使用锁

另一个互斥访问资源的方法就是使用锁

#include <stdio.h>
#include <omp.h>
int main(){
int sum=0;
int i ;
omp_lock_t lck ; //定义一把锁
omp_init_lock(&lck); //初始化一把锁
#pragma omp parallel for
for( i=1;i<=100;i++)
{
omp_set_lock(&lck); //给下面的sum上锁,同一时间只有一个线程能对sum变量操作
sum=sum+i;
omp_unset_lock(&lck); // 解锁
}
printf("sum=%d\n",sum);
omp_destroy_lock(&lck); //关闭这把锁
}
sum=5050

上述代码中,只定义的了一把锁,如果要定义多把锁,并使用多把锁,看下面的代码:

/*
*随机产生0~9之间1000个数,统计0~9的个数。
*histogram[]存放统计的个数
*
* */
#include <stdio.h>
#include <stdlib.h>
#include "omp.h"
int main ()
{
int array[1000];
omp_lock_t locks[10]; //定义10把锁
int histogram[10];
omp_set_num_threads (5);
srandom (10);
int i ;
#pragma omp parallel for
// 多线程随机产生1000个数放在array数组中
for ( i = 0; i < 1000; i++)
array[i] = random () % 10;
#pragma omp parallel for
// 多线程初始化10把锁和初始化histogram数组
for ( i = 0; i < 10; i++)
{
omp_init_lock (&locks[i]);
histogram[i] = 0;
}
#pragma omp parallel for
// 统计出现0~9的个数
for ( i = 0; i < 1000; i++)
{
omp_set_lock(&locks[array[i]]); //上锁
histogram[array[i]] += 1 ;
omp_unset_lock(&locks[array[i]]); //解锁
}
for ( i = 0; i < 10; i++)
printf ("histogram[%d]=%d\n", i, histogram[i]);
//普通方式(单线程)关闭10把锁
for ( i = 0; i < 10; i++)
omp_destroy_lock (&locks[i]); }
histogram[0]=97
histogram[1]=109
histogram[2]=95
histogram[3]=108
histogram[4]=89
histogram[5]=103
histogram[6]=85
histogram[7]=111
histogram[8]=110
histogram[9]=93

openMP编程,求pi的值

求pi的方法是利用积分推导出Pi的值,如下图所示:

/*
* 普通方式求Pi,不利用多线程技术
*/
#include <stdio.h>
static long num_steps = 100000;//分成1000份
void main()
{
int i;
double x, pi, sum = 0.0;
double step = 1.0/(double)num_steps;
for(i=1;i<= num_steps;i++){
x = (i-0.5)*step;
sum=sum+4.0/(1.0+x*x);
}
pi=step*sum;
printf("%lf\n",pi);
}
~
3.141593
/*
*利用 parallel for 进行多线程求解
* */
#include <stdio.h>
#include <omp.h>
static long num_steps = 100000;
double step;
#define NUM_THREADS 2
void main ()
{
int i;
double x, pi, sum[NUM_THREADS];
double step = 1.0/(double) num_steps;
omp_set_num_threads(NUM_THREADS); //设置2线程
#pragma omp parallel
{
double x;
int id;
id = omp_get_thread_num();
sum[id]=0;
#pragma omp for
for (i=0;i< num_steps; i++){
x = (i+0.5)*step;
sum[id] += 4.0/(1.0+x*x);
}
}
for(i=0, pi=0.0;i<NUM_THREADS;i++)
pi += sum[i] * step; printf("%lf\n",pi);
}

openMP编程(上篇)之并行程序设计的更多相关文章

  1. openMP编程(上篇)之指令和锁

    openMP简介 openMP是一个编译器指令和库函数的集合,主要是为共享式存储计算机上的并行程序设计使用的. 当计算机升级到多核时,程序中创建的线程数量需要随CPU核数变化,如在CPU核数超过线程数 ...

  2. 【CUDA并行程序设计系列(1)】GPU技术简介

    http://www.cnblogs.com/5long/p/cuda-parallel-programming-1.html 本系列目录: [CUDA并行程序设计系列(1)]GPU技术简介 [CUD ...

  3. OpenMP并行程序设计

    1.fork/join并行执行模式的概念 2.OpenMP指令和库函数介绍 3.parallel 指令的用法 4.for指令的使用方法 5 sections和section指令的用法 1.fork/j ...

  4. OpenMP并行程序设计——for循环并行化详解

    在C/C++中使用OpenMP优化代码方便又简单,代码中需要并行处理的往往是一些比较耗时的for循环,所以重点介绍一下OpenMP中for循环的应用.个人感觉只要掌握了文中讲的这些就足够了,如果想要学 ...

  5. 《CUDA并行程序设计:GPU编程指南》

    <CUDA并行程序设计:GPU编程指南> 基本信息 原书名:CUDA Programming:A Developer’s Guide to Parallel Computing with ...

  6. OpenMP 并行程序设计入门

    OpenMP 是一个编译器指令和库函数的集合,主要是为共享式存储计算机上的并行程序设计使用的. 0. 一段使用 OpenMP 的并行程序 #include <stdio.h> #inclu ...

  7. OpenMP编程总结表

    本文对OpenMP 2.0的全部语法——Macro(宏定义).Environment Variables(环境变量).Data Types(数据类型).Compiler Directives(编译指导 ...

  8. C#并发编程之初识并行编程

    写在前面 之前微信公众号里有一位叫sara的朋友建议我写一下Parallel的相关内容,因为手中商城的重构工作量较大,一时之间无法抽出时间.近日,这套系统已有阶段性成果,所以准备写一下Parallel ...

  9. 5天玩转C#并行和多线程编程 —— 第二天 并行集合和PLinq

    5天玩转C#并行和多线程编程系列文章目录 5天玩转C#并行和多线程编程 —— 第一天 认识Parallel 5天玩转C#并行和多线程编程 —— 第二天 并行集合和PLinq 5天玩转C#并行和多线程编 ...

随机推荐

  1. DHCP应用案列

    环境:centos7 需求:让员工实现0配置即可接入网络,但公司内部的若干文件服务器和打印机服务器需要使用固定的ip 部署dhcp服务很简单,首先安装dhcp,yum -y install dhcp. ...

  2. java集合类学习笔记之LinkedHashMap

    1.简述 LinkedHashMap是HashMap的子类,他们最大的不同是,HashMap内部维护的是一个单向的链表数组,而LinkedHashMap内部维护的是一个双向的链表数组.HashMap是 ...

  3. Python2和Python3共存,pip共存

    使用python开发,环境有Python2和 python3 两种,有时候需要两种环境切换使用,下面提供详细教程一份. 1.下载python3和python2 进入python官网,链接https:/ ...

  4. 海思hi35xx 开发学习(2):系统控制

    应用程序启动 MPP 业务前,必须完成 MPP 系统初始化工作.同理,应用程序退出MPP 业务后,也要完成 MPP 系统去初始化工作,释放资源. 视频缓存池 一组大小相同.物理地址连续的缓存块组成一个 ...

  5. NSInvocation 调用block clang代码转换c++

    NSInvocation 调用block cpp 转换 fatal error: 'UIKit/UIKit.h' file not found https://blog.csdn.net/yst199 ...

  6. Bad Smell (代码的坏味道)

    sourcemaking 如果一段代码是不稳定或者有一些潜在问题的,那么代码往往会包含一些明显的痕迹.正如食物要腐坏之前,经常会发出一些异味一样, 我们管这些痕迹叫做 "代码异味" ...

  7. 达人篇:2.1)APQP产品质量先期策划

    本章目的:介绍APQP的概念,明确APQP各个阶段提交的内容.理解APQP是帮助而不是负担. APQP概念: 产品质量先期策划(Advanced Product Quality Planning,简称 ...

  8. [转] git merge 将多个commit合并为一条之--squash 选项

    [FROM] https://blog.csdn.net/themagickeyjianan/article/details/80333645 1.一般的做法(直接git merge) Git相对于C ...

  9. Q806 写字符串需要的行数

    我们要把给定的字符串 S 从左到右写到每一行上,每一行的最大宽度为100个单位,如果我们在写某个字母的时候会使这行超过了100 个单位,那么我们应该把这个字母写到下一行.我们给定了一个数组 width ...

  10. Ubuntu中screen命令的使用

    参考GNU's Screen 官网:GNU's Scree screen的安装 sudo apt install screen 新建窗口1)可直接通过命令screen新建一个窗口,并进入窗口.但通过这 ...