golang 在多线程中避免 CPU 指令重排
发布日期:2024-03-26 16:29:39

起因
golang 的发明初衷便是多线程,是一门专门用于多线程高并发的编程语言。其独创的 GMP 模型在多线程的开发上提供了很大的便利。
现代计算机基本上都是多核 CPU 的结构。CPU 在进行指令运行的时候,为了提高效率,会在一些情况下对指令进行重排序,其目的是在保持运行结果和不重拍序的指令一致的前提下,提高程序的运行效率。但是对于多线程并行执行来说,我们可能需要对此额外关注,以避免重排对多线程的影响。
英特尔在其 x86/64 体系结构规范第 3 卷 §8.2.3 中列出了几个这样的问题。这里有一个最简单的例子。假设内存中有两个整数 X 和 Y,最初的值都是 0。两个并行运行的处理器执行以下的机器代码:

虽然在这个例子中使用汇编语言,但这确实是说明 CPU 排序的比较好的方式。每个处理器将 1 存储到其中一个整数变量中,然后将另一个整数加载到寄存器中。(r1 和 r2 只是实际 x86 寄存器(如 eax)的占位符名称。)
现在,无论哪个处理器先将 1 写入内存,都很自然地希望另一个处理器读取回该值,这意味着我们最终应该得到 r1=1、r2=1,或者两者都有。但根据英特尔的规范,情况不一定如此。在规范中,在这个例子的结尾,r1 和 r2 都等于 0 是合法的!这可能是一个违反直觉的结果!
理解这一点的一种方法是,与大多数处理器系列一样,英特尔x86/64处理器可以根据某些规则重新排序机器指令的内存交互,只要它永远不会改变单线程程序的执行。特别地,允许每个处理器将存储的效果延迟超过来自不同位置的任何加载。因此,最终可能会出现指令按以下顺序执行的情况:

程序测试
CPU 指令重排导致的问题
在下面的程序中,来实现上述 CPU 指令重排在多线程中造成的数据不一致现象。下面代码中,声明了 a,b,x,y 四个变量并将其默认值设置为 0。声明两个 go routine 分别执行目标操作(见代码)。正常情况,不管下面 a = 1,x = b,b = 1, y = a 这四条质量如何执行,如果没有重排产生,那么永远不可能出现 x == 0 和 y == 0 同时发生的情况。
但是由于 CPU 指令重排的原因,在实际执行的情况下,在第 1738, 110002, 12987 次测试到了 CPU 指令重排的发生。
func withCpuReordering() {index := 0for {index += 1var a, b int32 = 0, 0var x, y int32 = 0, 0var wg sync.WaitGroupwg.Add(2)go func() {defer wg.Done()a = 1x = b}()go func() {defer wg.Done()b = 1y = a}()wg.Wait()if x == 0 && y == 0 {panic("CPU Reordering occurs!")} else {fmt.Println("Now processing in loop", index)}}
}
绑定 CPU 消除指令重排
上述例子的现象只在多核 CPU 执行的之后才会出现,也就是线程并行执行的时候才会出现。如果我们将上述程序的执行都锁定在一个 CPU 上,也就能避免这种情况的发生。
在下面代码中,我们制定 go routine 最多只能使用一个 CPU。在整个测试过程中,没有出现 x == 0 和 y == 0 同时发生的情况。
func main() {runtime.GOMAXPROCS(1)withCpuReordering()
}
原因在于指令重排的目的在于提高执行效率,而不是改变执行结果。
通过内存屏障消除指令重排
在 Go 语言的 sync/atomic 包中,原子操作函数的实现会使用 CPU 提供的原子操作指令,以实现对共享变量的原子读写操作。这些原子操作指令通常会在硬件层面实现内存屏障(Memory Barrier),以确保对共享变量的读写操作在不同的 CPU 核心之间具有一定的有序性。
在下面的代码中,通过 atomic 包中的原子操作函数代替了上述代码中的赋值操作,从而解决了执行结果不一致的情况。
func withoutCpuReordering() {index := 0for {index += 1var a, b int32 = 0, 0var x, y int32 = 0, 0var wg sync.WaitGroupwg.Add(2)go func() {defer wg.Done()atomic.StoreInt32(&a, 1)atomic.StoreInt32(&x, atomic.LoadInt32(&b))}()go func() {defer wg.Done()atomic.StoreInt32(&b, 1)atomic.StoreInt32(&y, atomic.LoadInt32(&a))}()wg.Wait()if x == 0 && y == 0 {panic("CPU Reordering occurs!")} else {fmt.Println("Now processing in loop", index)}}
}
类似的指令和不同的平台
所有这些不同的 CPU 系列,每个都有独特的指令来强制执行内存排序,编译器根据不同的 CPU 系列将代码编译成不同的指令,并且每个跨平台项目都实现了自己的可移植层。这些都无助于简化无锁编程!这也是最近引入 C++11 原子库标准的部分原因。这是一种标准化的尝试,使编写可移植的无锁代码变得更容易。
比如 mfence 指令特定于 x86/64 的 CPU 架构。如果想使代码更具可移植性,可以将此内在特性封装在预处理器宏中。Linux 内核将其封装在一个名为 smp_mb 的宏,以及相关的宏中,如 smp_rmb 和 smp_wmb,并在不同的体系结构上提供了替代实现。例如,在 PowerPC 上,smp_mb 被实现为 sync。
参考文档:
[1] Memory Reordering Caught in the Act https://preshing.com/20120515/memory-reordering-caught-in-the-act/
相关文章:
golang 在多线程中避免 CPU 指令重排
发布日期:2024-03-26 16:29:39 起因 golang 的发明初衷便是多线程,是一门专门用于多线程高并发的编程语言。其独创的 GMP 模型在多线程的开发上提供了很大的便利。 现代计算机基本上都是多核 CPU 的结构。CPU 在进行指令运行的时候,为了提高…...
自动化更新包文件--shell脚本
自动化更新包文件--shell脚本 背景手动更包自动化更包 背景 作为一名实施工程师,当然也协助做些测试的工作,当产品功能开发后,研发会将本次迭代涉及的前后端包文件提供过来。有时会因为一些原因研发没法现场开发,那就需要我们配合…...
Vue element-plus 导航栏 [el-menu]
导航栏 [el-menu] Menu 菜单 | Element Plus el-menu有很多属性和子标签,为网站提供导航功能的菜单。 常用标签: 它里面有两个子标签。el-menu-item,它其实就是el-menu每一个里面的item,item就是真实匹配到路由的每个栏目&#…...
数据结构——数组
数组定义: 在计算机科学中,数组是由一组元素(值或变量)组成的数据结构,每个元素有至少一个索引或键来标识。 因为数组内的元素是连续存储的,所以数组中元素的地址,可以通过其索引计算出来。 性…...
python asyncio websockets server
python websocket server在收到接受消息处理完后会默认关闭连接。需要在msg_handler里面加个while true就能一直保持连接了。 start_server websockets.serve(msg_handler, "0.0.0.0", 29967) asyncio.get_event_loop().run_until_complete(start_server) asyncio.…...
视频素材免费网站有哪些?8个视频素材库网站下载推荐
在视频创作领域,选择正确的高质量无水印素材网站能够极大地丰富您的作品,让每一帧都鲜活起来。下面,我们继续为您介绍更多优质的视频素材网站,每一个都是您创作旅程中的宝贵资源。 1. 蛙学府(中国) 集合了…...
ChatGPT与传统搜索引擎的区别:智能对话与关键词匹配的差异
引言 随着互联网的快速发展,信息的获取变得比以往任何时候都更加便捷。在数字化时代,人们对于获取准确、及时信息的需求愈发迫切。传统搜索引擎通过关键词匹配的方式为用户提供了大量的信息,然而,这种机械式的检索方式有时候并不…...
xargs后调用bash自定义函数(写该函数文本到脚本, 并引导PATH)
xargs后调用bash自定义函数 需要3步骤,如下 function to_markdown_href_func() { fp$1 #echo $fpecho -e "\n[${fp}](${PREFIX}/${fp})" }BIN/tmp/bin/ F$BIN/to_markdown_href_func.sh mkdir -p $BIN 获得函数to_markdown_href_func的文本 ,写文本到 /tmp/bin/to_ma…...
学术论文写作新利器:ChatGPT技巧详解
ChatGPT无限次数:点击直达 学术论文写作新利器:ChatGPT技巧详解 在如今信息爆炸的时代,学术论文写作变得愈发重要且具有挑战性。随着人工智能技术的不断发展,ChatGPT作为一种强大的写作辅助工具,为学术论文创作者提供了全新的可能…...
Spring整合JDBC
1、引入依赖 <properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><depen…...
详解Qt中的布局管理器
Qt中的布局管理是用于组织用户界面中控件(如按钮、文本框、标签等)位置和尺寸调整的一种机制。说白了就是创建了一种规则,随着窗口变化其中的控件大小位置跟着变化。Qt提供了多种布局管理器,每种都有其特定用途和特点。以下是对Qt…...
MyBatis 参数重复打印的bug
现象 最近有个需求,需要在mybatis对数据库进行写入操作的时候,根据条件对对象中的某个值进行置空,然后再进行写入,这样数据库中的值就会为空了。 根据网上查看的资料,选择在 StatementHandler 类执行 update 的时候进…...
ES6学习之路:迭代器Iterator和生成器Generator
迭代器 一、知识背景 什么是迭代器 迭代器就是在一个数据集合中不断取出数据的过程迭代和遍历的区别 遍历是把所有数据都取出迭代器注重的是依次取出数据,它不会在意有多少数据,也不会保证能够取出多少或者能够把数据都取完。比如斐波那契额数列&#…...
如何使用 DynamiCrafter Interp Loop 无缝连接两张照片
DynamiCrafter Interp Loop 是一个基于 AI 的工具,可以用来无缝连接两张照片。它使用深度学习技术来生成中间帧,从而使两张照片之间的过渡更加自然流畅。 使用步骤 访问 DynamiCrafter Interp Loop 网站:https://huggingface.co/spaces/Dou…...
今天起,Windows可以一键召唤GPT-4了
ChatGPT狂飙160天,世界已经不是之前的样子。 新建了人工智能中文站https://ai.weoknow.com 每天给大家更新可用的国内可用chatGPT资源 发布在https://it.weoknow.com 更多资源欢迎关注 微软 AI 大计的最后一块拼图完成了? 把 Copilot 按钮放在 Window…...
使用Kaggle API快速下载Kaggle数据集
前言 在使用Kaggle网站下载数据集时,直接在网页上点击下载可能会很慢,甚至会出现下载失败的情况。本文将介绍如何使用Kaggle API快速下载数据集。 具体步骤 安装Kaggle API包 在终端中输入以下命令来安装Kaggle API相关的包: pip install…...
java 通过 microsoft graph 调用outlook(二)
这次提供一些基础调用方式API PS: getMailFolders 接口返回的属性中,包含了未读邮件数量unreadItemCount 一 POM文件 <!-- office 365 --><dependency><groupId>com.google.guava</groupId><artifactId>guava<…...
【机器学习】代价函数
🎈个人主页:豌豆射手^ 🎉欢迎 👍点赞✍评论⭐收藏 🤗收录专栏:机器学习 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进…...
[leetcode] 100. 相同的树
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 示例 1: 输入:p [1,2,3], q [1,2,3] 输出:true示例 2&a…...
08、Lua 函数
Lua 函数 Lua 函数Lua函数主要有两种用途函数定义解析:optional_function_scopefunction_nameargument1, argument2, argument3..., argumentnfunction_bodyresult_params_comma_separated 范例 : 定义一个函数 max()Lua 中函数可以作为参数传递给函数多返回值Lua函…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
