什么是 OpenMP

简易、快捷、高校、跨平台的线程 API 库 —— Tokisakix

进程和线程

进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。
线程:是进程的一个执行单元,是进程内的调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。
上面的解释可能还是有些抽象,但是通俗地解释方式就是:线程是进程的一个单元,如果把进程比作一座工厂,线程类似于工厂中的工人
这个比喻引用于此链接(点击跳转),可以在这里了解更多详细知识

共享内存的线程模型

共享内存的线程模型是指:

每个线程都可以有局部数据,也有共享的全局变量。多个线程共享相同的地址空间,每个线程都能访问全局内存。
线程的栈被分配在堆区,但只有主线程的栈可以动态增长,而子线程的栈是固定的。
线程之间的通信依靠共享的内存实现。

OpenMP 是什么?

OpenMP 是一套支持跨平台的共享内存方式的多线程并发的编程 API

可以理解为,OpenMP 就是一套可以插入在代码中的工具,我们利用这些工具来实现一些多线程相关的功能。

OpenMP 基于 Fork 、Join 模型,如下:

如图主线程 master thread 在进入并行区域(parallel region)时,线程会被分岔(fork),在并行区域(parallel region)结束后,这些线程最终又会合并(join)

而简化地说,OpenMP 的作用就是指导编译器应该在何时、以何种方式(细节)将程序编译,让编译器在可行的范围内实现更加高效的操作

OpenMP 的特性

编译指令 + 库函数的运用 —— Tokisakix

可以大致认为,OpenMP 的语法包括以下三类:

  • 线程相关的函数调用库

  • 线程相关的编译指导语句

  • 线程相关的环境变量

  • OpenMP 的执行有以下这些特点:

  • OpenMP 程序串行和并行区域交替出现

  • 串行区域由 master thread(thread 0) 执行

  • 并行区域由多个线程同一时间一起执行,不同线程一起完成并行区域中的任务

  • 如下图展示了 OpenMP 执行的特点

学习记录

L1

#pragma omp

  • 预编译指令

parallel

  • 说明下列代码段采用并行

private, shared

  • 设置并行区域的变量权限情况

omp_get_thread_num

  • 获取当前线程号

omp_get_num_threads

  • 获取当前线程数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <omp.h>

using namespace std;

int main_1() {
int tid, tnum;
#pragma omp parallel private(tid, tnum)
{
tid = omp_get_thread_num();
tnum = omp_get_num_threads();
printf("Hello OpenMP from thread %d in %d CPUs\n", tid, tnum);
}

return 0;
}

L2

omp_get_thread_num

  • 设置使用的线程数

for

  • 对接下来的 for 语句做并行循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <omp.h>

using namespace std;

int main_2() {
int tid, tnum;
omp_set_thread_num(10);
#pragma omp parallel for
for (int i = 0; i < 100; i++)
printf("Hello OpenMP from thread %d\n", omp_get_thread_num());

return 0;
}

L3

reduction

  • 对某变量做规约操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <omp.h>

using namespace std;

double f(double x) {
return 4 / (1 + x * x);
}

int main_3() {
double res = 0;
int n;

cout << "Enter N : ";
cin >> n;

#pragma omp parallel for reduction(+: res)
for (int i = 0; i < n; i++)
res += f((i - 0.5) / n);

res /= n;
printf("%.18lf\n", res);

return 0;
}

L4

schedule

  • 对并行循环进行模式设置,有四种模式
    1. 默认模式,各线程分管某一段连续的循环
    2. static 模式,各线程依次分管 step 个循环,step 可设置
    3. dynamic 模式,空闲线程自动分配 step 个循环,step 可设置
    4. guided 模式,类似 static 模式,step 由大变小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iostream>
#include <omp.h>

using namespace std;

void Static_() {
omp_set_num_threads(2);
int n, idx, i;

#pragma omp parallel for private(i) schedule(static)
for (i = 0; i < 10; i++) {
int n = omp_get_num_threads();
int idx = omp_get_thread_num();
printf("Nthreads:%d id:%d out:%d\n", n, idx, i);
}

return;
}

void Static_s() {
omp_set_num_threads(2);
int n, idx, i;

#pragma omp parallel for private(i) schedule(static, 4)
for (i = 0; i < 10; i++) {
int n = omp_get_num_threads();
int idx = omp_get_thread_num();
printf("Nthreads:%d id:%d out:%d\n", n, idx, i);
}

return;
}

void Dynamic_() {
omp_set_num_threads(2);
int n, idx, i;

#pragma omp parallel for private(i) schedule(dynamic)
for (i = 0; i < 10; i++) {
int n = omp_get_num_threads();
int idx = omp_get_thread_num();
printf("Nthreads:%d id:%d out:%d\n", n, idx, i);
}

return;
}

void Dynamic_s() {
omp_set_num_threads(2);
int n, idx, i;

#pragma omp parallel for private(i) schedule(dynamic, 4)
for (i = 0; i < 10; i++) {
int n = omp_get_num_threads();
int idx = omp_get_thread_num();
printf("Nthreads:%d id:%d out:%d\n", n, idx, i);
}

return;
}

void Guided_() {
omp_set_num_threads(2);
int n, idx, i;

#pragma omp parallel for private(i) schedule(guided)
for (i = 0; i < 10; i++) {
int n = omp_get_num_threads();
int idx = omp_get_thread_num();
printf("Nthreads:%d id:%d out:%d\n", n, idx, i);
}

return;
}

int main_4() {
Guided_();
return 0;
}

L5

critical

说明后续并行代码段是原子的

atomic

说明后续操作是原子的,比 critical 更高效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <omp.h>

using namespace std;

int main_5() {
int res = 0;
#pragma omp parallel for
for (int i = 0; i < 1000000; i++)
// #pragma omp atomic
#pragma omp critical
res++;

cout << res << endl;

return 0;
}