MyException - 我的异常网
当前位置:我的异常网» 编程 » openMP编程(上篇)之指令跟锁

openMP编程(上篇)之指令跟锁

www.MyException.Cn  网友分享于:2013-04-21  浏览:0次
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的值,如下图所示:

20170419-01

/*
 * 普通方式求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);
}

文章评论

相关解决方案
  • 暂无相关解决方案
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
如何成为一名黑客
如何成为一名黑客
鲜为人知的编程真相
鲜为人知的编程真相
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
我是如何打败拖延症的
我是如何打败拖延症的
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
老程序员的下场
老程序员的下场
Java程序员必看电影
Java程序员必看电影
旅行,写作,编程
旅行,写作,编程
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
代码女神横空出世
代码女神横空出世
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
一个程序员的时间管理
一个程序员的时间管理
编程语言是女人
编程语言是女人
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
中美印日四国程序员比较
中美印日四国程序员比较
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
 程序员的样子
程序员的样子
程序员必看的十大电影
程序员必看的十大电影
那些争议最大的编程观点
那些争议最大的编程观点
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
每天工作4小时的程序员
每天工作4小时的程序员
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
程序员应该关注的一些事儿
程序员应该关注的一些事儿
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
漫画:程序员的工作
漫画:程序员的工作
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
程序员的鄙视链
程序员的鄙视链
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
总结2014中国互联网十大段子
总结2014中国互联网十大段子
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
程序员都该阅读的书
程序员都该阅读的书
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
我的丈夫是个程序员
我的丈夫是个程序员
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
程序员和编码员之间的区别
程序员和编码员之间的区别
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有