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函…...
从Address Editor入手:在Block Design中精准调整Bram存储深度的实战解析
1. 当Bram存储深度无法修改时,你该怎么做? 第一次在Vivado中使用Block Design搭建系统时,很多人都会遇到一个奇怪的现象:明明在Bram IP核的参数设置界面看到了"Depth"这个选项,但无论如何点击都无法修改。这…...
CasRel开源大模型部署教程:一键拉取镜像+5分钟完成SPO推理
CasRel开源大模型部署教程:一键拉取镜像5分钟完成SPO推理 1. 什么是CasRel关系抽取模型 如果你需要从大段文字中自动找出"谁做了什么"、"谁是什么"这样的信息,CasRel模型就是你的得力助手。这个模型专门用来从文本中提取主体-谓语…...
OpenClaw+Qwen3-32B双剑合璧:个人知识库的智能维护方案
OpenClawQwen3-32B双剑合璧:个人知识库的智能维护方案 1. 为什么需要自动化知识管理 作为一个长期依赖个人知识库的内容创作者,我发现自己正陷入"信息过载"的困境。每天需要处理的网页文章、PDF报告、会议录音等碎片化内容超过20份ÿ…...
微软服软!被骂5年的Win11将被“整改”:告别强制更新、减少Copilot、任务栏摆放自由
整理 | 屠敏出品 | CSDN(ID:CSDNnews)Windows 11 自 2021 年发布以来,因任务栏功能缩水、UI 不统一、强制网络登录以及更高的硬件门槛,成为用户集中吐槽的焦点。再加上近来微软猛推 AI 功能,Copilot 的入口…...
2026 年终醒悟,AI 让我误以为自己很强,我思考了未来程序员的转型之路
2025 可以说只要是开发者都绕不过 AI ,时至今日你说你不用 AI 写代码我是不信的,但是直到最近我才发现,我似乎已经把 AI 的能力当做自己的能力,这种错觉体现在,昨天我用 AI 五分钟做出这下方这个动画效果: …...
别再只盯着Midjourney了!2025年,这5款文生图模型更适合你的具体业务场景
2025年五大文生图模型实战指南:如何为你的业务精准匹配AI工具 当Midjourney成为文生图领域的"网红"时,真正懂行的从业者已经在根据具体业务需求选择更合适的工具了。就像专业摄影师不会只用一款镜头拍所有题材,明智的AI应用者需要建…...
AHT20传感器数据漂移?STM32硬件I2C与软件模拟的稳定性对比测试
STM32硬件I2C与软件模拟I2C在AHT20传感器应用中的稳定性深度解析 工业级环境监测系统对温湿度数据的可靠性有着严苛要求。AHT20作为一款高精度温湿度传感器,其数据采集的稳定性直接关系到整个系统的可信度。本文将深入探讨STM32平台下硬件I2C与GPIO模拟I2C两种实现方…...
告别特征工程:用Python+Matplotlib把EEG脑电信号直接变成CNN能吃的时频图
从原始EEG到CNN输入:Python自动化生成时频图全流程解析 深夜的实验室里,显示器上跳动的脑电波形正被转化为一张张彩色图像——这不是科幻场景,而是现代脑机接口研究的日常。传统EEG分析中繁琐的特征工程正在被一种更直观的方法取代࿱…...
Java中高效移除文本文件标点符号的实用指南
本教程详细阐述了在Java中从文本文件中有效删除标点符号的方法。我们将使用Java NIO的Files.lines()结合Streamm API,重点介绍正则表达式p{Punct}强大的功能,以简单、强大的方式实现文本清洁,避免传统硬编码的局限性,从而提高文本…...
ChatTTS在政务热线场景落地:拟真语音提升市民服务体验真实案例
ChatTTS在政务热线场景落地:拟真语音提升市民服务体验真实案例 1. 项目背景与价值 政务热线是政府与市民沟通的重要桥梁,但传统语音系统存在明显痛点:机械化的语音播报缺乏人情味,长时间等待的提示音让市民感到烦躁,…...
