前面省去了基于共享变量的共享存储并行编程和POSIX线程,这两个在分布式系统中,应该是需要学习的点. 包括像面试时,分布式也有可能问到POSIX作用机制,还是应该留意一下. 这篇从OpenMP并行编程讲起.
OpenMP概述
历史
- 1994年,第一个ANSI X3H5草案被否决
- 1997年,OpenMP标准规范代替原先被否决的ANSI X3H5
- 1997年10月公布了与Fortran语言捆绑的第一个标准规范
- 1998年11月9日公布了支持C和C++的标准规范
- 2000年11月推出FORTRAN version a2.0。
- 2002年3月推出C/C++ version 2.0
- 2005年5月OpenMP2.5将原来的Fortran和C/C++标准规范相结合
- 更详尽的信息可以访问http://www.openmp.org
编程特点
- OpenMP应用编程接口API是在共享存储体系结构上的一个编程模型
- 包含编译制导、运行库例程和环境变量三个部分
- 已经被大多数计算机硬件和软件厂家所标准化
- 易于在不同的共享存储体系结构系统间移植
- 支持增量并行化
OpenMP编程风格
OpenMP并行编程模型
- 基于线程的并行编程模型
- 一个外部的编程模型:程序员完全控制并行化
- 使用FORK-JOIN并行执行模型
- 通过使用编译制导语句来实现并行
OpenMP中的HelloWorld
这里使用windows下的visual studio编写运行OpenMP程序十分容易,新建一个控制台程序,点击项目属性 - C/C++ - 语言 - OpenMP支持. 如果是在Linux下,使用gcc或g++选项编译:gcc -fopenmp -o a.out a.c
,也就是加上-fopenmp
选项.
编写如下代码:
#include <iostream>
#include <omp.h>
using namespace std;
int main() {
int pid;
omp_set_num_threads(4); // 设置4个线程
#pragma omp parallel private(pid) // 进入并行域,每个线程私有pid
{
pid = omp_get_thread_num();
cout << "The current running process is " << pid << endl;
if (pid == 0) { // 只有最初父进程可以进入这个代码块
cout << "Now I'm in Main Process!!" << endl;
int total_num = omp_get_num_threads();
cout << "And I have " << total_num << " children" << endl;
}
}
return 0;
}
运行结果:
这里就注意一下:omp_set_thread_nums
、omp_get_thread_num
和 omp_get_num_threads
的函数调用,以及编译制导语句#pragma omp parallel private(pid)
的使用.
并行域结构
- 当并行域开始时,多个线程都会执行并行域中的代码
- 当并行域结束时,只有主线程继续执行
- 并行域的线程数由下列因素决定,且优先级递减:
- 使用库函数omp_set_num_threads
- 设置环境变量OMP_NUM_THREADS
- 由实现决定的缺省值
- 并行域中的线程号依次为0(主线程)到n-1
运行库例程
- OpenMP定义了一套API对外提供多种库函数调用
- C/C++程序需要引用文件<omp.h>
- “Hello World”程序中出现的两个例程:
- omp_get_thread_num:得到当前执行线程在并行域中的线程号
- omp_get_num_threads:得到当前并行域使用的线程数
环境变量
- OMP_SCHEDULE:for或parallel for中的调度方式
- OMP_NUM_THREADS:执行中最大的线程数
- OMP_DYNAMIC:是否动态设定并行域执行部分的线程数
- OMP_NESTED:是否允许嵌套并行
共享任务结构
- 将它所包含的代码划分给线程组的各成员来执行
- 三种典型的共享任务结构:
- for:代表数据并行性
- sections:代表功能并行
- single
for语句 指定紧随它的循环语句必须由线程组并行执行
scedule子句:
- 描述如何将循环的迭代划分给线程组中的线程
- 如果没有指定chunk的大小,迭代会尽可能地平均分配给每个线程
- type为static:循环被分成大小为 chunk的块,静态分配给线程
- type为dynamic:循环被划分为大小为chunk的块,动态分配给线程
#include <iostream>
#include <omp.h>
#define N 100
using namespace std;
int a[N], b[N], c[N];
int main() {
int i;
omp_set_num_threads(4);
#pragma omp parallel shared(a,b,c) private(i)
{
#pragma omp for
for (i = 0; i < N; ++i) {
cout << "in Thread " << omp_get_thread_num() << ", excute " << i << endl;
c[i] = a[i] + b[i];
}
}
return 0;
}
在哪看处理器数?任务管理器,有几个小窗口,就有几核.
OpenMP其他编程要素
同步结构
多个线程同时直接读写共享变量而导致错误的结果.
例如,下面程序对x进行共享读写而不加任何保护,会导致奇怪的结果,这在Java多线程编程中我们也遇到过.
#include <iostream>
#include <omp.h>
using namespace std;
const int N = 1000000;
int main() {
int i;
int x;
omp_set_num_threads(4);
#pragma omp parallel shared(x) private(i)
{
#pragma omp for
for (i = 0; i < N; ++i) {
++x;
}
}
cout << x << endl;
return 0;
}
单线程执行,x的值就是 1000000,但程序执行的结果是:362451,显然就是发生了读写不同步.