【OpenMP学习笔记】1:基本使用和critical指令

2023-12-09 14:48

本文主要是介绍【OpenMP学习笔记】1:基本使用和critical指令,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

MPI是可以针对分布式内存,在进程级别实现并行的API;OpenMP则是针对共享内存,在线程级别实现并行的API。

基本使用

不同于MPI的init和finalize,OpenMP用携带了parallel指令的预处理指令指示接下来的一个代码块被多个线程执行。

OpenMP预处理指令-(携带)->OpenMP指令-(携带)->OpenMP指令的子句。

隐式路障是OpenMP的一个特点,所有执行这个代码块的线程在同一个线程组中以隐式路障隐式同步。

#include<stdio.h>
#include<stdlib.h>//检查是否定义了_OPENMP预处理宏,即编译器是否支持OpenMP
#ifdef _OPENMP 
#include<omp.h> //支持时包含OpenMP头文件
#endifint main(int argc,char *argv[])
{//stdlib的strtol()函数把字符串转换成整形数//参数1:字符串首地址,跳过前面的空格符,遇到数字或正负符号才开始做转换//参数2:用于引用返回遇到非数字或'\0'结束转换时的字符指针所在地址//参数3:转换至几进制,取值2~36或0,取0时即默认为10进制int thrdCnt=strtol(argv[1],NULL,0);printf("strtol()函数从主函数参数解析得的线程数:%d\n",thrdCnt);
//# pragma omp指示后面接的是OpenMP的预处理指令//parallel指令表明之后的结构化代码块被多个线程执行//num_threads(线程数)子句指定执行后代码块的线程数
#   pragma omp parallel num_threads(thrdCnt){//如果不支持OpenMP,前面没有包含omp.h,则里面的函数也不可用//所以要在所有使用了omp.h的代码处做包含检查
#       ifdef _OPENMP//omp_get_thread_num()函数获得该线程的编号int myThrdNm=omp_get_thread_num();//omp_get_num_threads()函数获得总的线程数int allNmThrds=omp_get_num_threads();
#       else//当不能使用OpenMP时,只有一个线程,线程号是0int myThrdNm=0;//当不能使用OpenMP时,只有一个线程,线程数目是1int allNmThrds=1;
#       endif//为了验证"隐式路障",此处sleep()不同的时间,不妨按线程编号延时sleep(myThrdNm*3);//C语言里sleep()的单位是秒printf("我是%d/%d\n",myThrdNm,allNmThrds);}//隐式路障:完成代码块的线程要等待其它所有线程完成此代码块printf("路障解除了\n");return 0;
}

输出

[lzh@hostlzh OpenMP]$ !gcc
gcc -fopenmp -o hello.o hello.c
[lzh@hostlzh OpenMP]$ ./hello.o 4
strtol()函数从主函数参数解析得的线程数:4
我是0/4
我是1/4
我是2/4
我是3/4
路障解除了
[lzh@hostlzh OpenMP]$

critical指令

OpenMP里变量的作用域可以按照块内是否共享分为共享作用域私有作用域。对于并行代码块而言,在块前声明的变量缺省作用域是共享的(不过这个”共享”当然不能穿透函数)。

某些地方未使用critical时可能存在的问题

当块内共同操作了共享的资源时,不对其做互斥保护就可能会在运行时出问题。因为只能保证一次汇编级的指令是原子的,甚至不能保证一条C语言语句在并发执行过程中不会发生线程的切换,在并行的情况下就更加危险了:

#include<stdio.h>
#include<stdlib.h>
#include<omp.h>int main(int argc,char *argv[])
{int a=20;int thrdCnt=strtol(argv[1],NULL,10);
#   pragma omp parallel num_threads(thrdCnt)    a=a-1;//并行块中访问了共享变量aprintf("a=%d\n",a);//最终输出看一下return 0;
}

输出

[lzh@hostlzh OpenMP]$ !gcc
gcc -fopenmp -o test1.o test1.c
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=6
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5

才执行了这么几次就发生了出错,可见问题严重。

使用critical时
#include<stdio.h>
#include<stdlib.h>
#include<omp.h>int main(int argc,char *argv[])
{int a=20;int b=20;int thrdCnt=strtol(argv[1],NULL,10);
#   pragma omp parallel num_threads(thrdCnt){//OpenMP指令critical安排线程对接下来的代码块互斥访问
#       pragma omp criticala=a-1;//共享变量a受critical保护互斥访问b=b-1;//共享变量b未受critical保护}printf("a=%d\nb=%d\n",a,b);//最终输出看一下return 0;
}

输出

[lzh@hostlzh OpenMP]$ !gcc
gcc -fopenmp -o test1.o test1.c
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=6
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ ./test1.o 15
a=5
b=5
[lzh@hostlzh OpenMP]$ 

可见受critical指令保护的a能够正常执行了,而不受保护的b仍然会发生前面的问题。

这篇关于【OpenMP学习笔记】1:基本使用和critical指令的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/474087

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

k8s按需创建PV和使用PVC详解

《k8s按需创建PV和使用PVC详解》Kubernetes中,PV和PVC用于管理持久存储,StorageClass实现动态PV分配,PVC声明存储需求并绑定PV,通过kubectl验证状态,注意回收... 目录1.按需创建 PV(使用 StorageClass)创建 StorageClass2.创建 PV

Redis 基本数据类型和使用详解

《Redis基本数据类型和使用详解》String是Redis最基本的数据类型,一个键对应一个值,它的功能十分强大,可以存储字符串、整数、浮点数等多种数据格式,本文给大家介绍Redis基本数据类型和... 目录一、Redis 入门介绍二、Redis 的五大基本数据类型2.1 String 类型2.2 Hash

Redis中Hash从使用过程到原理说明

《Redis中Hash从使用过程到原理说明》RedisHash结构用于存储字段-值对,适合对象数据,支持HSET、HGET等命令,采用ziplist或hashtable编码,通过渐进式rehash优化... 目录一、开篇:Hash就像超市的货架二、Hash的基本使用1. 常用命令示例2. Java操作示例三

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他