openMP编程(上篇)之并行程序设计
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编程(上篇)之并行程序设计的更多相关文章
- openMP编程(上篇)之指令和锁
		
openMP简介 openMP是一个编译器指令和库函数的集合,主要是为共享式存储计算机上的并行程序设计使用的. 当计算机升级到多核时,程序中创建的线程数量需要随CPU核数变化,如在CPU核数超过线程数 ...
 - 【CUDA并行程序设计系列(1)】GPU技术简介
		
http://www.cnblogs.com/5long/p/cuda-parallel-programming-1.html 本系列目录: [CUDA并行程序设计系列(1)]GPU技术简介 [CUD ...
 - OpenMP并行程序设计
		
1.fork/join并行执行模式的概念 2.OpenMP指令和库函数介绍 3.parallel 指令的用法 4.for指令的使用方法 5 sections和section指令的用法 1.fork/j ...
 - OpenMP并行程序设计——for循环并行化详解
		
在C/C++中使用OpenMP优化代码方便又简单,代码中需要并行处理的往往是一些比较耗时的for循环,所以重点介绍一下OpenMP中for循环的应用.个人感觉只要掌握了文中讲的这些就足够了,如果想要学 ...
 - 《CUDA并行程序设计:GPU编程指南》
		
<CUDA并行程序设计:GPU编程指南> 基本信息 原书名:CUDA Programming:A Developer’s Guide to Parallel Computing with ...
 - OpenMP 并行程序设计入门
		
OpenMP 是一个编译器指令和库函数的集合,主要是为共享式存储计算机上的并行程序设计使用的. 0. 一段使用 OpenMP 的并行程序 #include <stdio.h> #inclu ...
 - OpenMP编程总结表
		
本文对OpenMP 2.0的全部语法——Macro(宏定义).Environment Variables(环境变量).Data Types(数据类型).Compiler Directives(编译指导 ...
 - C#并发编程之初识并行编程
		
写在前面 之前微信公众号里有一位叫sara的朋友建议我写一下Parallel的相关内容,因为手中商城的重构工作量较大,一时之间无法抽出时间.近日,这套系统已有阶段性成果,所以准备写一下Parallel ...
 - 5天玩转C#并行和多线程编程 —— 第二天 并行集合和PLinq
		
5天玩转C#并行和多线程编程系列文章目录 5天玩转C#并行和多线程编程 —— 第一天 认识Parallel 5天玩转C#并行和多线程编程 —— 第二天 并行集合和PLinq 5天玩转C#并行和多线程编 ...
 
随机推荐
- [ActionScript 3.0] 绘制扇形方法
			
/** * 绘制扇形 * @param mc 承载扇形的对象 * @param x 圆心角x * @param y 圆心角y * @param r 半径 * @param angle 绘制角度 * @ ...
 - linux校验时间
			
一. 基本方法:date -s "2018-09-02 04:23:45"clock -w 把时间写入cmos(一个RAM芯片) hwclock 查看硬件时间 hwclock ...
 - docker搭建elk+cerebro环境
			
ELK的帮助手册 Docker Hub官网:https://hub.docker.com/r/sebp/elk/ Docker ELK使用文档:http://elk-docker.readthedoc ...
 - 用API爬取天气预报数据
			
1.注册免费API和阅读技术文档: 注册地址:https://console.heweather.com 文档地址:https://www.heweather.com/documents/api-ur ...
 - git泄露利用脚本
			
留一下万一之后用得着呢 工作原理 1.解析.git/index文件,找到工程中所有的: ( 文件名,文件sha1 ) 2.去.git/objects/ 文件夹下下载对应的文件 3.zlib解压文件,按 ...
 - 配置mybatis解决log4j未正常打印sql的问题
			
在mybatis-config.xml中增加配置: <settings> <setting name="logImpl" value="STDOUT_L ...
 - Java 网络通信相关
			
http://m.blog.csdn.net/xiaojin21cen/article/details/78587541 越下面越底层 , 最后面的都是框架 , 下面的是 编程语言提供的库的 NIO ...
 - 设置开机自动运行vncserver
			
a. 在/etc/rc.d/rc.local文件中加入下面行 /etc/init.d/vncserver startb. 编辑/etc/sysconfig/vncservers VNCSERV ...
 - vue2.0修饰符sync用法
			
如果子组件是一个弹窗,我们想通过点击关闭按钮来关闭子组件弹窗,子组件弹窗的v-show由变量isVisible控制,这个变量通过props由父组件来注入, 而子组件无法改变props里面的变量的值,但 ...
 - PHP设置时区
			
<?php//设置默认的时区date_default_timezone_set('Asia/Shanghai');//输出1396193923对应的日期echo date("Y-m-d ...