OpenMP
第一部分:基本知识
1、编译制导
编译制导法:也称为智能编译,它是隐式并行策略的体现,主要是由并行编译系统进行程序表示、控制流的分析、相关分析、优化分析和并行化划分,由相关分析得到方法库管理方案,由优化分析得到知识库管理方案,由并行化划分得到程序重构,从而形成并行程序。
OpenMP编译制导指令以#pragma omp 开始,后边跟具体的功能指令,格式如:
#pragma omp 指令[子句],[子句] …]
注意:后面接的大括号({ )需要另起一行;
1.1、 指令
指令 | 解释 |
---|---|
parallel | 用在一个代码段之前,表示这段代码将被多个线程并行执行 |
for | 用于for循环之前,将循环分配到多个线程中并行执行,必须保证每次循环之间无相关性。 |
parallel for | parallel 和 for语句的结合,也是用在一个for循环之前,表示for循环的代码将被多个线程并行执行 |
parallel sections | 用在可被并行执行的代码段之前,用于实现多个结构块语句的任务分担,可并行执行的代码段各自用section指令标出(注意区分sections和section) |
critical | 用在一段代码临界区之前 |
single | 用在一段只被单个线程执行的代码段之前,表示后面的代码段将被单线程执行 |
flush | 保证各个OpenMP线程的数据影像的一致性 |
barrier | 用于并行区内代码的线程同步,所有线程执行到barrier时要停止,直到所有线程都执行到barrier时才继续往下执行 |
atomic | 用于指定一块内存区域被自动更新 |
master | 用于指定一段代码块由主线程执行 |
ordered | 用于指定并行区域的循环按顺序执行 |
task | 申请一个线程去执行指定区域的代码 |
1.2、子句
子句 | 解释 |
---|---|
private | 指定一个或多个变量在每个线程中都有它自己的私有副本 |
firstprivate | 指定一个或多个变量在每个线程都有它自己的私有副本,并且私有变量要在进入并行域或任务分担域时,继承主线程中的同名变量的值作为初值 |
lastprivate | 是用来指定将线程中的一个或多个私有变量的值在并行处理结束后复制到主线程中的同名变量中,负责拷贝的线程是for或sections任务分担中的最后一个线程 |
reduction | 用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的归约运算,并将结果返回给主线程同名变量 |
nowait | 指出并发线程可以忽略其他制导指令暗含的路障同步 |
num_threads | 指定并行域内的线程的数目 |
schedule | 指定for任务分担中的任务分配调度类型 |
shared | 指定一个或多个变量为多个线程间的共享变量 |
ordered | 用来指定for任务分担域内指定代码段需要按照串行循环次序执行 |
copyprivate | 配合single指令,将指定线程的专有变量广播到并行域内其他线程的同名变量中 |
copyin | 用来指定一个threadprivate类型的变量需要用主线程同名变量进行初始化 |
default | 用来指定并行域内的变量的使用方式,缺省是shared |
2、库函数
通过调用OpenMP里面的API实现并行(类似平常使用的函数)。
库函数 | 解释 |
---|---|
omp_get_num_procs | 返回运行本线程的多处理机的处理器个数。 |
omp_get_num_threads | 返回当前并行区域中的活动线程个数。 |
omp_get_thread_num | 返回线程号 |
omp_set_num_threads | 设置并行执行代码时的线程个数 |
omp_init_lock | 初始化一个简单锁 |
omp_set_lock | 上锁操作 |
omp_unset_lock | 解锁操作,要和omp_set_lock函数配对使用。 |
omp_destroy_lock | omp_init_lock函数的配对操作函数,关闭一个锁 |
第二部分:详细说明
1、#pragma omp parallel
使用parallel指令只是产生了并行域,在没有其他配合指令的情况下只是让多个线程分别执行相同的任务,没有实际价值。
代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include"omp.h"
#include<iostream>
#include<ctime>
#include<Windows.h>
using namespace std;
int main() {
#pragma omp parallel num_threads(5)
{
// 基于当前系统的当前日期/时间
time_t now = time(0);
// 把 now 转换为字符串形式
char* dt = ctime(&now);
cout << "Thread: " << omp_get_thread_num() << "\tTime: " << dt;
Sleep(2000);
}
return 0;
}
结果:
Thread: 2 Time: Tue May 11 22:35:08 2021
Thread: 4 Time: Tue May 11 22:35:08 2021
Thread: 0 Time: Tue May 11 22:35:08 2021
Thread: 1 Time: Tue May 11 22:35:08 2021
Thread: 3 Time: Tue May 11 22:35:08 2021
2、#pragma omp parallel for
该语句告知编译器,紧接着的for循环可以被并行执行,每次循环之间不能有关系,另外可以让系统默认分配线程个数,也可以使用num_threads子句指定线程个数。
- 循环中迭代次数必须在执行循环前就可以算出
- 循环中不能包含break,return或exit
- 循环中不能包含前往循环外的goto语句
代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include"omp.h"
#include<iostream>
#include<time.h>
#define MAX_NUM 100000000
using namespace std;
int main() {
clock_t start_time = clock();
for (int i = 0; i < MAX_NUM; i++) {
i* i;
}
clock_t end_time = clock();
cout << "串行运行时间:" << static_cast<double> (end_time - start_time) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
start_time = clock();
#pragma omp parallel for
for(int i = 0; i < MAX_NUM; i++) {
i* i;
}
end_time = clock();
cout << "并行运行时间:" << static_cast<double> (end_time - start_time) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
return 0;
}
结果:
串行运行时间:182ms
并行运行时间:30ms
3、#pragma omp sections & #pragma omp parallel sections
#pragma omp sections 串行执行, #pragma omp parallel sections 才是并行执行
代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include"omp.h"
#include<iostream>
#include<time.h>
#include<Windows.h>
using namespace std;
void thread_time() {
time_t now = time(0);
char* dt = ctime(&now);
cout << "Thread: " << omp_get_thread_num() << "\tTime: " << dt;
Sleep(2000);
}
int main() {
omp_set_num_threads(2);
#pragma omp sections
{
#pragma omp section
{thread_time(); }
#pragma omp section
{thread_time(); }
}
cout << endl;
#pragma omp parallel sections
{
#pragma omp section
{thread_time(); }
#pragma omp section
{thread_time(); }
}
return 0;
}
结果:
Thread: 0 Time: Tue May 11 22:36:47 2021
Thread: 0 Time: Tue May 11 22:36:49 2021
Thread: 0 Time: Tue May 11 22:36:51 2021
Thread: 1 Time: Tue May 11 22:36:51 2021
4、#pragma omp single
在使用了 #pragma omp single 之后,后面指定区域只有一个线程去执行,即使前面使用了#pragma omp parallel num_threads(5) 指定线程数为5;
代码:使用#pragma omp single
#define _CRT_SECURE_NO_WARNINGS 1
#include"omp.h"
#include<iostream>
#include<time.h>
#include<Windows.h>
using namespace std;
void thread_time() {
time_t now = time(0);
char* dt = ctime(&now);
cout << "Thread: " << omp_get_thread_num() << "\tTime: " << dt;
}
int main() {
#pragma omp parallel num_threads(5)
{
#pragma omp single
{thread_time(); }
}
return 0;
}
结果:
Thread: 0 Time: Tue May 11 22:43:59 2021
代码:去掉#pragma omp single
#define _CRT_SECURE_NO_WARNINGS 1
#include"omp.h"
#include<iostream>
#include<time.h>
#include<Windows.h>
using namespace std;
void thread_time() {
time_t now = time(0);
char* dt = ctime(&now);
cout << "Thread: " << omp_get_thread_num() << "\tTime: " << dt;
}
int main() {
#pragma omp parallel num_threads(5)
{
thread_time();
}
return 0;
}
结果:
Thread: 1 Time: Tue May 11 22:45:31 2021
Thread: 3 Time: Tue May 11 22:45:31 2021
Thread: 0 Time: Tue May 11 22:45:31 2021
Thread: 4 Time: Tue May 11 22:45:31 2021
Thread: 2 Time: Tue May 11 22:45:31 2021
5、#pragma omp task
在递归算法中,可以利用 #pragma omp task 不断申请线程去执行递归程序;
#include"omp.h"
#include<iostream>
#include<unistd.h>
#include<ctime>
using namespace std;
void task(int parent_thread, int count) {
if(count > 2)
return;
count++;
time_t timel;
time(&timel);
int child_thread = omp_get_thread_num();
cout << "Thread " << child_thread << " from Thread " << parent_thread << "\tTime: " << asctime(gmtime(&timel));
sleep(1);
#pragma omp task
{task(child_thread, count); }
#pragma omp task
{task(child_thread, count); }
}
int main(int argc, char *argv[]){
int count = 0;
#pragma omp parallel num_threads(5)
{
#pragma omp single
{task(omp_get_thread_num(), count); }
}
return 0;
}
结果:
Thread 1 from Thread 1 Time: Wed May 12 11:41:52 2021
Thread 1 from Thread 1 Time: Wed May 12 11:41:53 2021
Thread 2 from Thread 1 Time: Wed May 12 11:41:53 2021
Thread 1 from Thread 1 Time: Wed May 12 11:41:54 2021
Thread 0 from Thread 1 Time: Wed May 12 11:41:54 2021
Thread 2 from Thread 2 Time: Wed May 12 11:41:54 2021
Thread 4 from Thread 2 Time: Wed May 12 11:41:54 2021
从结果可知:第一秒,线程1执行task;第二秒,在 #pragma omp task 处申请了一个新线程,两个线程并行执行;第三秒,两个线程分别申请一个新线程,四个线程并行。
参考文献
[1]初步认识并行计算openmp – elimuzi
[2]OpenMp之sections用法 – 很厉害的名字
[3]并行与分布式计算导论(五)OpenMP基础详解 - 大选帝侯
[4]【OpenMP】parallel for使用多线程进行并行加速 - 颜丑文良777