使用openmp进行并行编程

预处理指令pragma

在系统中加入预处理器指令通常是用来容许不是基本c语言规范部分的行为。不支持pragma的编译器会忽略pragma指令提示的那些语句,这样就容许使用pragma的程序在不支持它们的平台上运行。c++

第一个程序:hello

#include <stdio.h>
#include <stdlib.h>
#include <omp.h>

void Hello(void); // Thread function

int main(int argc, char* argv[]) {  
	// Get number of threads from command line  
	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 %dnn\n", my_rank, thread_count);
}

Hello例子的分析:

最基本的并行原语

用于运行代码块的线程数能够动态生成。程序员

pragma omp parallel  :

当程序到达parallel指令时,原来的线程继续执行,另外的线程被启动。在openmp语法中,执行并行块的线程集合(原始线程和新的线程被称为线程组,原始的线程被称为主线程,额外的线程称为从线程。每一个线程组成员都调用指令后的代码块。算法

num_thread( )

# pragma omp parallel num_threads ( thread_count )

一个从句例子(用于修饰原语),可用于指定线程数量app

omp.h

#include <omp.h>

使用openmp必须含omp.h头文件ide

strtol( )

long strtol(const char* number p,char** end p,int base);

使用stdlib.h中的strtol来得到线程数
ps:一些系统因素可能会限制能够启动的线程数量;OpenMP 并不保证可以启动指定个的线程;函数

多数系统可以启动上百甚至上千的线程;除非启动的太多,通常都能知足要求。ui

例子:梯形积分法

图片

若是每一个子区间有相同的宽度,而且定义h=(b-a)/n,xi=a+ih,i=0, 1, ..., n,那么近似值将是:线程

图片

//串行算法实现//
 Input: a, b, n ;
 h = (b*a)/n;
 approx = (f(a) + f(b))/2.0;
 for (i = 1; i <= n-1; i++) {
 	x_i = a + i*h;
 	approx += f(x_i);
 }
 approx = h*approx;

第一种尝试

  1. 定义两种类型的任务:

​ a) 计算单个梯形的面积;设计

​ b) 将面积加起来。指针

  1. 在第一阶段,没有通讯开销;但第二阶段每一个任务须要通讯。

考虑一个问题:结果不可预估——引入互斥量

pragma omp critical   global_result += my_result ;

第一个版本

 #include <stdio.h>
 #include <stdlib.h>
 #include <omp.h>

  void Trap(double a, double b, int n, double  global_result p);
  int main(int argc, char  argv[]){
    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 integral from %f to %f = %.14e n",
    a, b, global_result);
    return 0;
}    /∗  main ∗/
    
    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;
   #  pragma omp critical
      ∗global_result_p += my_result;
  }    /∗  Trap ∗/

做用域

在串行程序中, 变量的做用域包含了全部可使用变量的区域;

在OpenMP中, 变量的做用域还要包括能够访问该变量的并行区域。

能被全部线程访问的变量具备 shared(共享) 做用域;

只能被一个线程访问的变量具备 private (私有)做用域.

默认的做用域是 shared.

规约从句:

替代(在parallel块中声明一个私有变量和将临界区移到函数调用之)

归约:将相同的归约操做符重复的应用到操做数序列来获得一个结果的计算。

全部操做的中间结果存储在一个变量中:归约变量

reduction(<operator>:<variable list>)

新的代码:

global_result = 0.0;
#  pragma omp parallel num threads(thread count)\
 reduction(+: global_result)
global_result += Local_trap(double a, double b, int n);

parallel for

可以生成一队线程来执行接下来的语句块;

语句块必须是一个for循环;

经过将循环切分给不一样的线程来实现并行。

只有迭代次数肯定的循环才能够被并行化。

h = (b−a)/n;
approx = (f(a) + f(b))/2.0;
#  pragma omp parallel for num threads(thread_count) reduction(+: approx)
for (i = 1; i <= n−1; i++)
approx += f(a + i∗h); approx = h∗approx;

可被并行化的for循环形式:
图片

**ps: **index 必须是整数或者指针 (e.g., 不能是浮点数);

start, end, 和 incr 必须具备相应的类型。 例如, 若是index 是一个指针, 那么 incr 必须是一个整型;

start, end, 和 incr 在循环执行过程当中不能被修改;

在循环执行过程当中, 变量 index 只能被for语句修改。

数据依赖

1.OpenMP 编译器并不检查循环迭代中的数据依赖问题;

2.通常来讲,OpenMP没法处理带有数据依赖的循环。

解决思路:设计私有变量而且保证其私有做用域(private子句)

default子句

编译器强制要求程序员指定在块中使用的外部变量的做用范围。

double sum = 0.0;
# pragma omp parallel for num threads(thread count)\
default(none) reduction(+: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);
}

for指令

并不建立线程,使用已经在parallel块中建立的线程。

#  pragma omp for

解决循环调用问题:schedule ( type , chunksize )

type 能够是:
static: 提早把任务分配好;

dynamic or guided: 在运行时动态分配;

dynamic:

任务被分红 chunksize 大小的连续段;

每一个线程执行一小块, 当有一个线程执行完时, 它会请求得到1个新的;

重复上述过程,直到完成计算;

chunksize 能够被去掉;当去掉时, chunksize 默认为1.

guided:

每一个线程执行一小块, 当有一个线程执行完时, 它会请求得到1个新的;

可是,新的任务块是不断变小的;

若是不指定chunksize,那么默认会降到1.

若是指定了chunksize, 则会降到指定的chunksize, 除了最后一块可能小于chunksize.

auto: 编译器或者运行时系统决定调度策略;

runtime: 运行时决定。

chunksize 是一个正整数