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

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

openMP

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

  • OpenMP的pragma总是以 ##pragma omp 开始
简单例子
#include 
#include 
#include 

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)
openMP 函数总结(并行程序设计导论)_第1张图片
  • 线程被同一个进程派生,这些线程共享大部分资源。有它自己的计数器。当一个线程完成了执行,它就又合并到启动它的线程中。
  • 每个线程都有它自己的栈,所以执行一个代码块将在代码块内创建自己的私有局部变量
-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
#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 : +,*,-,&,|,^,&&,||
用法
  • 当一个变量包含在一个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指令并行化的循环中,循环变量的缺省作用域是私有的,每个线程会有它自己的循环变量副本
openMP 函数总结(并行程序设计导论)_第2张图片
合法方式
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
数据依赖性
  • OpenMP编译器不检查parallel for指令并行化的循环所包含的迭代间的依赖关系,而是由程序员来识别这些依赖。
  • 一个或更多个迭代结果依赖于其他迭代的循环,一般不能被OpenMP正确地并行化。
数据依赖
#y依赖于x
for(i=0;i
循环依赖

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

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

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;
}
openMP 函数总结(并行程序设计导论)_第3张图片
- 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
- schedule子句

对线程进行调度。

语法
schedule( [,]

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=;
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 
#include 
#include 
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

获取运行时间。

你可能感兴趣的:(openMP 函数总结(并行程序设计导论))