OpenMP 入门
OpenMP 入门
简介
OpenMP 一个非常易用的共享内存的并行编程框架,它提供了一些非常简单易用的API,让编程人员从复杂的并发编程当中释放出来,专注于具体功能的实现。openmp 主要是通过编译指导语句以及他的动态运行时库实现,在本篇文章当中我们主要介绍 openmp 一些入门的简单指令的使用。
认识 openmp 的简单易用性
比如现在我们有一个任务,启动四个线程打印 hello world,我们看看下面 C 使用 pthread 的实现以及 C++ 使用标准库的实现,并对比他们和 openmp 的实现复杂性。
C 语言实现
#include <stdio.h>
#include <pthread.h>
void* func(void* args) {
printf("hello world from tid = %ld\n", pthread_self());
return NULL;
}
int main() {
pthread_t threads[4];
for(int i = 0; i < 4; i++) {
pthread_create(&threads[i], NULL, func, NULL);
}
for(int i = 0; i < 4; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
上面文件编译命令:gcc 文件名 -lpthread 。
C++ 实现
#include <thread>
#include <iostream>
void* func() {
printf("hello world from %ld\n", std::this_thread::get_id());
return 0;
}
int main() {
std::thread threads[4];
for(auto &t : threads) {
t = std::thread(func);
}
for(auto &t : threads) {
t.join();
}
return EXIT_SUCCESS;
}
上面文件编译命令:g++ 文件名 lpthread 。
OpenMP 实现
#include <stdio.h>
#include <omp.h>
int main() {
// #pragma 表示这是编译指导语句 表示编译器需要对下面的并行域进行特殊处理 omp parallel 表示下面的代码区域 {} 是一个并行域 num_threads(4) 表示一共有 4 个线程执行 {} 内的代码 因此实现的效果和上面的效果是一致的
#pragma omp parallel num_threads(4)
{
printf("hello world from tid = %d\n", omp_get_thread_num()); // omp_get_thread_num 表示得到线程的线程 id
}
return 0;
}
上面文件编译命令:gcc 文件名 -fopenmp ,如果你使用了 openmp 的编译指导语句的话需要在编译选项上加上 -fopenmp。
从上面的代码来看,确实 openmp 写并发程序的复杂度确实比 pthread 和 C++ 低。openmp 相比起其他构建并行程序的方式来说,使用 openmp 你可以更加关注具体的业务实现,而不用太关心并发程序背后的启动与结束的过程,OenpMP 会帮我们实现很多细节,让程序的执行符合我们的直觉。
opnemp 基本原理
在上文当中我们写了一个非常简单的 openmp 程序,使用 4 个不同的线程分别打印 hello world 。我们仔细分析一下这个程序的执行流程:

在 openmp 的程序当中,你可以将程序用一个个的并行域分开,在并行域(parallel region)中,程序是有并发的,但是在并行域之外是没有并发的,只有主线程(master)在执行,整个过程如下图所示:

现在我们用一个程序去验证上面的过程:
#include <stdio.h>
#include <omp.h>
#include <unistd.h>
int main() {
#pragma omp parallel num_threads(4)
{
printf("parallel region 1 thread id = %d\n", omp_get_thread_num());
sleep(1);
}
printf("after parallel region 1 thread id = %d\n", omp_get_thread_num());
#pragma omp parallel num_threads(4)
{
printf("parallel region 2 thread id = %d\n", omp_get_thread_num());
sleep(1);
}
printf("after parallel region 2 thread id = %d\n", omp_get_thread_num());
#pragma omp parallel num_threads(4)
{
printf("parallel region 3 thread id = %d\n", omp_get_thread_num());
sleep(1);
}
printf("after parallel region 3 thread id = %d\n", omp_get_thread_num());
return 0;
}
程序执行之后的一种输出(还有很多其他的输出形式,因为是多线程程序,线程的输出是不确定的)如下所示:
parallel region 1 thread id = 0
parallel region 1 thread id = 3
parallel region 1 thread id = 1
parallel region 1 thread id = 2
after parallel region 1 thread id = 0
parallel region 2 thread id = 0
parallel region 2 thread id = 2
parallel region 2 thread id = 3
parallel region 2 thread id = 1
after parallel region 2 thread id = 0
parallel region 3 thread id = 0
parallel region 3 thread id = 1
parallel region 3 thread id = 3
parallel region 3 thread id = 2
after parallel region 3 thread id = 0
从上面的输出我们可以了解到,id = 0 的线程就是主线程,在并行域内部程序的输出是没有顺序的,但是在并行域的外部是有序的,在并行域的开始部分程序会进行并发操作,但是在并行域的最后会有一个隐藏的同步点,等待所有线程到达这个同步点之后程序才会继续执行,现在再看上文当中 openmp 的执行流图的话就很清晰易懂了。
积分例子
现在我们使用一个简单的函数积分的例子去具体了解 openmp 在具体的使用场景下的并行。比如我们求函数 \(x^2\) 的积分。

\]
比如我们现在需要 x = 10 时,\(x^2\) 的积分结果。我们在程序里面使用微元法去计算函数的微分结果,而不是直接使用公式进行计算,微元法对应的计算方式如下所示:
\]
微元法的本质就是将曲线下方的面积分割成一个一个的非常小的长方形,然后将所有的长方形的面积累加起来,这样得到最终的结果。

如果你不懂上面所谈到的求解方法也没关系,只需要知道我们需要使用 openmp 去计算一个计算量比较大的任务即可。根据上面微元法的公式我们有一个非常大的求和公式,如果是在单线程的情况下我们使用一个循环就可以了,但是现在我们有多个线程,那么我们可以让每个线程求某一个区间的和,最后将各个区间的和加起来得到最终的结果,这就是在并发场景下的实现思路。
openmp 具体的实现代码如下所示:
#include <stdio.h>
#include <omp.h>
#include <math.h>
/// @brief 计算 x^2 一部分的面积
/// @param start 线程开始计算的位置
/// @param end 线程结束计算的位置
/// @param delta 长方形的边长
/// @return 计算出来的面积
double x_square_partial_integral(double start, double end, double delta) {
double s = 0;
for(double i = start; i < end; i += delta) {
s += pow(i, 2) * delta;
}
return s;
}
int main() {
int s = 0;
int e = 10;
double sum = 0;
#pragma omp parallel num_threads(32) reduction(+:sum)
{
// 根据线程号进行计算区间的分配
// omp_get_thread_num() 返回的线程 id 从 0 开始计数 :0, 1, 2, 3, 4, ..., 31
double start = (double)(e - s) / 32 * omp_get_thread_num();
double end = (double)(e - s) / 32 * (omp_get_thread_num() + 1);
sum = x_square_partial_integral(start, end, 0.0000001);
}
printf("sum = %lf\n", sum);
return 0;
}
在上面的代码当中 #pragma omp parallel num_threads(4) 表示启动 4 个线程执行 {} 中的代码,reduction(+:sum) 表示需要对 sum 这个变量进行一个规约操作,当 openmp 中的线程遇到 reduction 子句的时候首先会拷贝一份 sum 作为本地变量,然后在并行域当中使用的就是每一个线程的本地变量,因为有 reduction 的规约操作,因此在每个线程计算完成之后还需要将每个线程本地计算出来的值对操作符 + 进行规约操作,也就是将每个线程计算得到的结果求和,最终将得到的结果赋值给我们在 main 函数当中定义的变量 sum 。最终我们打印的变量 sum 就是各个线程求和之后的结果。上面的代码执行过程大致如下图所示:

注意事项:你在编译上述程序的时候需要加上编译选项 -fopenmp 启动openmp 编译选项和 -lm 链接数学库。
上面程序的执行结果如下所示:

总结
在本篇文章当中主要给大家介绍了 OpenMP 的基本使用和程序执行的基本原理,在后续的文章当中我们将仔细介绍各种 OpenMP 的子句和指令的使用方法,希望大家有所收获!
更多精彩内容合集可访问项目:https://github.com/Chang-LeHung/CSCore
关注公众号:一无是处的研究僧,了解更多计算机(Java、Python、计算机系统基础、算法与数据结构)知识。
OpenMP 入门的更多相关文章
- OpenMP入门教程(三)
承接前面两篇,这里直接逐一介绍和使用有关OpenMP的指令和函数 Directives 1.for 作用:for指令指定紧随其后的程序的循环的迭代必须由团队并行执行,只是假设已经建立了并行区域,否则它 ...
- OpenMP入门
OpenMP入门 前情提要:并行(parallel):需要多个运算核心同时完成 其中有多处理器和单处理器多核两种实现方式,其中差异如下: 同一芯片上的多核通信速度更快 同一芯片上的多核能耗更低 Ope ...
- OpenMP 入门教程
前两天(其实是几个月以前了)看到了代码中有 #pragma omp parallel for 一段,感觉好像是 OpenMP,以前看到并行化的东西都是直接躲开,既然躲不开了,不妨研究一下: OpenM ...
- 并行计算之OpenMP入门简介
在上一篇文章中介绍了并行计算的基础概念,也顺便介绍了OpenMP. OpenMp提供了对于并行描述的高层抽象,降低了并行编程的难度和复杂度,这样程序员可以把更多的精力投入到并行算法本身,而非其具体实现 ...
- openmp入门总结
Ref: https://wdxtub.com/2016/03/20/openmp-guide/ 简介 这门课作为 ECE 中少有的跟计算机科学相关的课,自然是必上不可.不过无论是 OpenMP 还是 ...
- [转]OpenMP 入门指南
简介 这门课作为 ECE 中少有的跟计算机科学相关的课,自然是必上不可.不过无论是 OpenMP 还是 CUDA,对于平时极少接触并行编程的我来说,都是十分吃力的,第一次作业的 OpenMP 编程已经 ...
- OpenMP入门教程(二)
OpenMP API概述 OpenMP由三部分组成: 编译指令(19) 运行时库程序(32) 环境变量(9) 后来的API包含同样的三个组件,只是三者的数量都有所增加. 编译器指令 OpenMP编译器 ...
- OpenMP入门教程(一)
什么是OpenMP Open Multi-Processing的缩写,是一个应用程序接口(API),可用于显式指导多线程.共享内存的并行性. 在项目程序已经完成好的情况下不需要大幅度的修改源代码,只需 ...
- openMP多线程编程
OpenMP(Open Muti-Processing) OpenMP缺点: 1:作为高层抽象,OpenMp并不适合需要复杂的线程间同步和互斥的场合: 2:另一个缺点是不能在非共享内存系统(如计算机集 ...
随机推荐
- C#基础_C#计算样本标准差和总体标准差
首先我们先了解样本标准差和总体标准差: 样本标准差=方差的算术平方根=s=sqrt(((x1-x)^2 +(x2-x)^2 +......(xn-x)^2)/(n-1)) 总体标准差=σ=sqrt(( ...
- 长篇图解etcd核心应用场景及编码实战
大家好啊,我是字母哥,今天写一篇关于etcd的文章,其实网上也有很多关于etcd的介绍,我就简明扼要,总结提炼,期望大家通过这一篇文章掌握etcd的核心知识以及编码技能! 本文首先用大白话给大家介绍一 ...
- 写给前端的 react-native 入门指南
前言 本文主要介绍 react-native(下称 RN) 的入门, 和前端的异同点 文章不涉及功能的具体实现 选择优势 我们先说说, 为什么很多人会选择使用 RN .他对应的特性和普通 Web 的区 ...
- SpringMVC 07: WEB-INF下的资源访问 + SpringMVC拦截器
WBE-INF目录下的资源访问 项目配置和Spring博客集(指SpringMVC 02)中配置一样 出于对网站资源的安全性保护,放在WBE-INF目录下的资源不可以被外部直接访问 在WEB-INF/ ...
- C语言怎么给函数添加形参的默认值
以下内容为本人的著作,如需要转载,请声明原文链接微信公众号「englyf」https://www.cnblogs.com/englyf/p/16637890.html 如果不是机缘巧合,当年转到C++ ...
- 让Python更优雅更易读(第二集)
友情链接 让Python更优雅更易读(第一集) 1.装饰器 1.1装饰器特别适合用来实现以下功能 运行时校验:在执行阶段进行特定校验,当校验通不过时终止执行. 适合原因:装饰器可以方便地在函数执行前介 ...
- torch.meshgrid
1:https://blog.csdn.net/weixin_39504171/article/details/106356977 2: https://pytorch.org/docs/stable ...
- haodoop数据压缩
压缩概述 压缩技术能够有效减少底层存储系统(HDFS)读写字节数.压缩提高了网络宽带和磁盘空间的效率.在运行MR程序时,I/O操作,网络数据传输,Shuffle和Merge要花大量的时间,尤其是数据规 ...
- MyBatis标签之Select resultType和resultMap
摘要:介绍MyBatis 中Select标签的两个属性resultType和resultMap及其区别. 1 MyBatis动态SQL之if 语句 2 MyBatis动态sql之where标签|转 3 ...
- 使用 Loki 收集 nginx 日志
转载自:https://mp.weixin.qq.com/s?__biz=MzU4MjQ0MTU4Ng==&mid=2247492075&idx=1&sn=ba63984111 ...