openMP 函数总结(并行程序设计导论)

本篇文章只是记录api的用法和回顾,方便记忆

openMP

openMP提供“基于指令”的共享内存API。这就意味着在c和c++中,有一些特殊的预处理指令pragma。在系统中加入预处理指令一般时用来允许不是基本C语言的规范的行为。
不支持pragma的编译器会忽略pragma指令提示的那些语句,这样就允许使用pragma的程序在不支持它的平台上运行

  • OpenMP的pragma总是以 ##pragma omp 开始
简单例子
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>

void Hello(void);
int main(int argc,char* argv[])
{       
         /*
             long strtol( 
                        const char* number_p *in*, 第一个参数是字符串
                        const char** end_p  *out*,终止的非法字符串 
                        int     base  *in* 进制(2-36)
                        )
            
            例:
            char buffer[20]="10379cend$3";
            char *stop;
            printf("%d\n",strtol(buffer, &stop, 2));
            printf("%s\n", stop);
            输出结果:
            2
            379cend$3
        */
        int thread_count = strtol(argv[1],NULL,10);
#pragma omp parallel num_threads(thread_count)
        Hello();
        
        return 0;
}

void Hello(void)
{
    int my_rank = omp_get_thread_num();
    int thread_count = omp_get_num_threads();

    printf("hello from thread %d of %d \n",my_rank,thread_count);
}


#编译
gcc -g -Wall -fopenmp -o main main.c

#-g :产生供gdb调试用的可执行文件
# http://www.jianshu.com/p/30ffc01380a0
#-Wall:编译后显示所有警告
#-fopenmp 使用mpi支持
#-o:输出到指定文件


#pragma omp pallel
  • 使用parallel是用来表明之后的结构化代码块(一个结构化代码块时一条C语句或者只有一个入口一个出口的一组复合C语句)应该被多个线程并行执行。
  • 完成代码块前会有一个隐式路障,先完成的线程必须等待线程组其他线程完成代码块。
- num_threads 子句
  1. 允许程序员指定执行后代码块的线程数
  2. 程序可以启动的线程数可能会受系统定义的限制。OpenMP标准并不保证实际能够启动thread_count个线程
#pragma omp parallel num_threads(thread_count)
  • 线程被同一个进程派生,这些线程共享大部分资源。有它自己的计数器。当一个线程完成了执行,它就又合并到启动它的线程中。
  • 每个线程都有它自己的栈,所以执行一个代码块将在代码块内创建自己的私有局部变量
-func omp_get_thread_num | omp_get_num_threads
#获得当前线程的编号
int omp_get_thread_num(void)
#获得线程数量
int omp_get_num_threads(void)
错误检查

可以通过预处理宏_OPENMP是否定义。

#ifdef _OPENMP
#include<omp.h>
#endif

#ifdef _OPENMP
    int my_rank=omp_get_thread_num();
    int thread_count=omp_get_num_threads();
#else
    int my_rank=0;
    int thread_count=1;
#endif
#pragma omp critical
  • 只有一个线程能够执行对应代码块,并且第一个线程完成操作前,没有其他的线程能够开始执行这段代码。
  • 当不添加name时,OpenMP默认做法将所有临界区代码块作为符合临界区一部分,添加name后两个不同名字的cirtical指令保护的代码可以同时执行
语法
#pragma omp critical [(name)]
用法
#pragma omp critical
global_result += my_result;
变量的作用域
  • 在parallel块之前被声明的变量的缺省作用域时共享的。
  • parallel指令前已经被声明的变量,拥有线程组中所有线程间的共享作用域,而在块中声明的变量(例如,函数中的变量)中有私有作用域
- reduction 规约子句
语法
reduction(<operator>:<variable list>)
# operator : +,*,-,&,|,^,&&,||
用法
  • 当一个变量包含在一个reduction子句中时,变量本身是共享的。然而,线程组中的每个线程都创建自己的私有变量。在parallel块里,每当一个线程执行涉及这个变量(共享变量)的语句时,它使用的其实时私有变量。当parallel块执行结束后,私有变量中的值被整合到一个共享变量中。
  • 如果一个规约变量时floatdouble变量型数据,那么当使用不同数量的线程时,结果可能有些许不同。这是由于浮点数运算不满足结合律
  • OpenMp会为此创建一个临界区,并且在这个临界区中,将存储在私有变量中的值进行相加(或其他operator)。
global_result=0.0;
#pragma omp parallel num_threads(thread_count) reduction(+:global_result)
global_result += Local_trap(double a,double b,int n);

####等同

global_result=0.0;
#pragma omp parallel num_threads(thread_count) 
{
    double my_result =0.0;/*私有变量*/
    my_result += Local_trap(double a,double b,int n);
#pragma omp critial
    global_result += Local_trap(double a,double b,int n);
}

#pragma omp parallel for
  • parallel for 指令生成一组线程来执行后面的结构化代码块(必须是for循环)。
  • 系统通过在线程间划分循环迭代来并行化for循环。与parallel指令非常不同,因为在parallel指令之前的块,一般来说其工作必须由线程本身在线程之间划分。
  • 在一个已经被parallel for指令并行化的for循环中,线程间的缺省划分方式由系统决定(大约 m(迭代次数)/thread_count)。
  • 在一个被parallel for指令并行化的循环中,循环变量的缺省作用域是私有的,每个线程会有它自己的循环变量副本
合法方式
h=(b-a)/n;
approx =(f(a)+f(b))/2.0;
# pragma omp parallel for num_threads(thread_count) reduction(+:=approx)
approx += f(a+i*h);
approx = h* approx;
线程重用
  • 与parallel指令不同的是,for指令并不创建任何线程。它使用已经在parallel块中创建的线程。在循环的末尾有一个隐式的路障
#pragma omp parallel num_threads(thread_count) default(none) \ 
  shared(a,n) private(i,tmp,phase)
for(phase = 0;phase<n;phase++)
{
    if(phase%2 == 0)
      #pargma omp for
      for(i=1;i<n;i++)
        ...
    else
      #pargma omp for
      for(i=1;i<n;i++)
        ...
}  
数据依赖性
  • OpenMP编译器不检查parallel for指令并行化的循环所包含的迭代间的依赖关系,而是由程序员来识别这些依赖。
  • 一个或更多个迭代结果依赖于其他迭代的循环,一般不能被OpenMP正确地并行化。
数据依赖
#y依赖于x
for(i=0;i<n;i++)
{
    x[i]=a+i*h;
    y[i]=exp(x[i]);
}
循环依赖

一个值在循环中计算,其结果在之后迭代中使用。

#并行化后某一个边界值将是另一个并行化线程中的使用。
fibo[0]=fibo[1]=1;
for(i=2;i<n;i++)
    fibo[i]=fibo[i-1]+fibo[i-2];
- private 子句
  • 在private子句列举的变量,在每个线程上都有一个私有副本被创建
  • 一个私有作用域的变量的值在parallel块或者parallel for块的开始处是未指定的。它的值在parallel块或者parallel for块完成之后也是未指定的。
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>

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

    int x=5;

    #pragma omp parallel private(x)
    {
        int my_rank =omp_get_thread_num();
        printf("Thread %d > before initialization,x=%d \n",my_rank,x);
        x=2*my_rank+2;
        printf("Thread %d > after initialization,x=%d \n",my_rank,x);
    }

    printf("after parallel,x=%d \n",x);
        return 0;
}
- default(none) 子句
  • 让程序员明确块中每个变量的作用域。
double sum = 0.0;
/*
sum是一个规约变量(同时拥有私有和共享作用域的属性)。
*/
#pragma omp parallel for num_threads(thread_count) \
  default(none) redcution(+:sum) private(k,factor) \
  shared(n)
  for(k=0;k<n;k++)
    if(k%2 ==0)
        factor = 1.0;
    else
        factor = -1.0;
    sum += factor/(2*K+1);
- schedule子句

对线程进行调度。

语法
schedule(<type> [,<chunsize>]

type可以时一下的任意一个。

  • static。迭代能够在循环执行前分配给线程。
(static,1)
Thread0:0,3,6,9
Thread1:1,4,7,10
Thread2:2,5,8,11

(static,2)
Thread0:0,1,6,7
Thread1:2,3,8,9
Thread2:4,5,10,11

缺省调度(static,total_iterations/thread_count)
  • dynamic或guided。迭代在循环执行时被分配给线程,因此在一个线程完成了它的当前迭代集合后,他能从运行时系统中请求更多。
dynamic调度中,迭代也被分成chunksize个连续迭代的块。
每个线程执行一块,并且当一个线程完成一块时,
他将从运行时系统请求另一块,直到所有的迭代完成。
chunksize可以被忽略。当它被忽略时,chunksize为1。
在guided调度中,每个线程也执行一块,并且当一个线程完成一块,将请求另一块。
然而,在guided调度中,当块完成后,新块的大小变小。
例如:
n=10 000并且thread_count=2时,迭代将如表那样分配。块的大小近似等于的迭代数除以线程数。第一块的大小为9999/2 ~=5000,因为9999个未被分配的迭代。第二块的大小为4999/2~=2500。以此类推。

| 线程 |    块    | 快的大小 | 剩下的迭代代数 |
| 0   | 1~5000   | 5000 | 4999 |
| 1   | 5001-7500| 2500 | 2499 |
| 1   | 7501-8750| 1250 | 1249 |
...
  • auto。编译器和运行时系统决定调度方式。

  • runtime。调度在运行时决定。
    chunksize是一个正整数。在OpenMP中,迭代块在顺序循环中连续执行的一块迭代语句,块中的迭代次数时chunsize。只有static,dynamic和guided调度有chunksize。

设置环境变量
$export OMP_SCHEDULE="static,1"
#pragma omp barrier
  • 显式的路障,当所有的线程都到达了这个路障时,这些线程就可以接着往下执行。
#pragma omp atomic
  • 只能保护由一条C语言赋值语句所形成的临界区,是一个更高效的指令

语句必须是以下形式:

#op:+,*,-,/,&,^,|,<<,or >> .
#expreesion不能引用x。
x<op>=<expreesion>;
x++;
++x;
x--;
y--;

用法
#其他线程对x的更新必须等到该线程对x的更新结束之后。
#但对y不受保护,因此程序的结果是不可预测的。
#pragma omp atomic
  x+=y++
简单锁
  • 第一个函数初始化锁,所以锁此时处于解锁状态。
  • 第二个函数尝试获得锁,如果成功,调用该函数的线程可以继续执行,如果失败调用该函数的线程被阻塞,直到锁被其他线程释放。
  • 第三个函数释放锁,以便其他线程获得该锁。
  • 第四个函数销货锁。
void omp_init_lock(omp_lock_t*  lock_p  /*out*/);
void omp_set_lock(omp_lock_t*  lock_p  /*in/out*/);
void omp_unset_lock(omp_lock_t*  lock_p  /*in/out*/);
void omp_destroy_lock(omp_lock_t*  lock_p  /*in/out*/);
用法
static omp_lock_t lock;   
void test11()  
{  
    omp_init_lock(&lock); // 初始化互斥锁    
  
#pragma omp parallel for    
    for (int i = 0; i < 5; ++i)     
    {    
        omp_set_lock(&lock); //获得互斥器     
        std::cout << omp_get_thread_num() << "+" << std::endl;    
        std::cout << omp_get_thread_num() << "-" << std::endl;    
        omp_unset_lock(&lock); //释放互斥器    
    }    
    omp_destroy_lock(&lock); //销毁互斥器    
}  
#pragma omp single

这样做能确保接下来的结构化代码块由线程组中的一个线程执行,而组内其他线程等待直到该线程执行结束(在代码块的最后设置一个隐式路障)

#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
int main()
{

#pragma omp parallel
{
int my_rank = omp_get_thread_num();
if(my_rank == 1)
{
   int x=1;
   while(x<1e9)
    {
        x+=1;
    }
}
#pragma omp single
    printf("%d \n",my_rank);
printf("----> %d \n",my_rank);

}
    return 0;
}

#pragma omp master

这样能确保线程0执行接下来的结构化代码块。然后master指令在最后不会设置隐式路障

-func omp_get_wtime

获取运行时间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容