《并行程序设计导论》01openmp

预备知识

Openmp提供“基于指令”的共享内存API。这意味着在c和c++中,有一些特殊的预处理器指令pragma。不支持pragma的编译器就会忽略pragma指令提示的那些语句,这样就允许使用pragma的程序在不支持他们的平台上运行。因此,在理论上,如果你仔细编写一个openmp程序,他就能够在任何有c编译器的系统上被编译和运行,无论编译器是否支持openmp。
一个使用openmp的hello world程序

#include<stdio.h>
#include<stdlib.h>
#include<omp.h>
void hello(void);
int main(int argc,char* argv[])
{
    int thread_count=strtol(argv[1],NULL,10);//第一个参数从命令行获得线程数,第二个参数没用,第三个表示10进制
# pragma omp parallel num_threads(thread_count);//parallel指令,用来表明之后的结构化代码块
    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编译这个程序,需要包含-fopenmp选项

 gcc -g -Wall -fopenmp -o omp_hello omp_hello.c

运行程序

./omp_hello 4

注意到线程正在竞争访问标准输出,因此不保证输出按照正常序号输出

程序

除了指令集合外,openmp是有一个函数和宏库组成。
在一般的调用线程时,我们必须写很多代码来派生和合并多个线程:需要为每个线程的特殊结构来分别陪存储空间,因此需要for循环来启动每个线程,并使用另一个for循环来终止这些线程。线程被同一进程派生,这些线程共享他们的进程的大部分资源,但每个线程有自己的栈和程序计数器。

openmp总是以##pragma开始(注意,不支持pragma的编译器会自动忽略它,所以openmp程序可以在任何的c编译器运行),最基本的parallel指令可以如下简单的形式:

#pragma omp parallel

运行结构化代码块(要并行的c语句)的线程数将由运行时系统决定。如果没有其他线程启动,典型情况下会将在每个核上运行一个线程。
如果想要在指定线程数,通常会在命令行里指定,在pragma指令增加num_threads子句。在openmp中,子句是用来修改指令的文本。

#pragma omp parallel num_threads(thread_count)

需要注意,程序可以启动的线程数可能会受到系统定义的限制。openmp标准并不保证实际情况下能启动thread_count个线程.
程序运行流程:
在到达parallel之前,程序只使用一个线程。
当程序开始执行,进程开始启动,到达parallel指令时,原来的线程继续执行,另外thread_count-1个线程被启动。
执行并行块的线程集合(原始线程和新的线程)称为线程组,原始的线程称为主线程,额外的线程称为从线程。每个线程都会调用hello函数。
在代码块执行完时,当线程从hello返回时,有一个隐式路障。完成代码快的线程将等待线程组中的所有其他线程完成代码块。
当所有线程都完成代码块,从进程终止,主线程继续执行之后的代码。

因为每个线程有自己的栈,所以执行一个hello函数的线程将在函数中创建自己的私有局部变量。

int omp_get_num_threads(void);//得到线程数
int omp_get_thread_num(void);//得到线程号

错误检查

首先一定要检查命令行参数的存在,如果存在,调用strtol后应该检查值是否是正数。还要检查被parallel指令实际创建的线程数和thread_count是否一样。
第二个潜在风险的来源是编译器。要去诶的那个编译器是否支持openmp。可以检查预处理器宏_OPENMP是否定义。
在头文件处

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

在并行代码块处

# ifdef _OPENMP
         int my_rank=omp_get_thead_num()int thread_count=omp_get_num_threads()# else
         int my_rank=0int thread_count=1# endif

梯形积分法

第一个openmp梯形积分法程序

#include<stdio.h>
#include<stdlib.h>
void Trap(double a,double b,int n,double* global_result_p);
 
int main()
{
    double global_result=0.0;
    double a,b;
    int n;
    int thread_count;
    thread_count=strtol(argv[1],NULL,10);
    printf("enter a,b, and n\n");
    scanf("%lf %lf %d",&a,&b,&n);
 #pragma omp parallel num_threads(thread_count)
    trap(a,b,n,&global_result);
 
    printf("With n=%d trapezoids, our estimate\n",n);
    printf("of the intergral from %f to %f =%.14e\n",a,b,global_result);
    return 0;
 
}

void trap(double a,double b,int n,double* global_result_p)
{
    double h,x,my_result;
    double local_a,local_b;
    int i,local_n;
    int my_rank=omp_get_thread_num();
    int thread_count=omp_get_num_threads();
    h=(b-a)/n;
    local_n=n/thread_count;
    local_a=a+my_rank*local_n*h;
    local_b=local_a+local_n*h;
    my_result=(f(local_a)+f(local_b))/2.0;
    for(i=1;i<=local_n-1;i++)
    {
        x=local_a+i*h;
        my_result+=f(x);
    }
    my_result=my_result*h;
   
    /
    *使用一个共享变量作为所有线程之和,每个线程可以将它计算的部分结果累加到共享变量中。
    然而这可能会导致一个错误的global_result值——如果两个线程试图同时执行这条语句。
    除非一个线程在其他线程开始时就完成了计算*global_result_p+=my_result,否则结果都将是不正确的。
    这其实是一个竞争条件的例子:多个线程试图访问一个共享资源,并且至少其中一个访问是更新该共享资源,这可能会出现错误。
    */
 #pragma omp critical//在openmp中使用critical指令,这条指令告诉编译器需要安排线程对下列的代码块进行互斥访问,即一次只有一个线程能够执行下面的结构化代码
    *global_result_p+=my_result;
    //引起竞争条件的代码称为临界区。临界区是一个被多个更新共享资源的线程执行的代码,并且共享资源一次只能被一个线程更新
}

解释上面的程序:
在main函数中,第14行前面的代码都是单线程的,它简单获取线程数和输入。在第16行里,parallel指令明确trap函数应该被thread_count个线程执行。在从trap调用返回后,任何被parallel启动的新线程都将终止,程序只用一个线程恢复执行。这个线程打印结果并终止。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值