golang 在多线程中避免 CPU 指令重排

发布日期:2024-03-26 16:29:39

pF5Lfc6.jpg

起因

golang 的发明初衷便是多线程,是一门专门用于多线程高并发的编程语言。其独创的 GMP 模型在多线程的开发上提供了很大的便利。

现代计算机基本上都是多核 CPU 的结构。CPU 在进行指令运行的时候,为了提高效率,会在一些情况下对指令进行重排序,其目的是在保持运行结果和不重拍序的指令一致的前提下,提高程序的运行效率。但是对于多线程并行执行来说,我们可能需要对此额外关注,以避免重排对多线程的影响。

英特尔在其 x86/64 体系结构规范第 3 卷 §8.2.3 中列出了几个这样的问题。这里有一个最简单的例子。假设内存中有两个整数 X 和 Y,最初的值都是 0。两个并行运行的处理器执行以下的机器代码:

pF5LR91.png

虽然在这个例子中使用汇编语言,但这确实是说明 CPU 排序的比较好的方式。每个处理器将 1 存储到其中一个整数变量中,然后将另一个整数加载到寄存器中。(r1 和 r2 只是实际 x86 寄存器(如 eax)的占位符名称。)

现在,无论哪个处理器先将 1 写入内存,都很自然地希望另一个处理器读取回该值,这意味着我们最终应该得到 r1=1、r2=1,或者两者都有。但根据英特尔的规范,情况不一定如此。在规范中,在这个例子的结尾,r1 和 r2 都等于 0 是合法的!这可能是一个违反直觉的结果!

理解这一点的一种方法是,与大多数处理器系列一样,英特尔x86/64处理器可以根据某些规则重新排序机器指令的内存交互,只要它永远不会改变单线程程序的执行。特别地,允许每个处理器将存储的效果延迟超过来自不同位置的任何加载。因此,最终可能会出现指令按以下顺序执行的情况:

pF5LW1x.png

程序测试

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 := 0
	for {
		index += 1

		var a, b int32 = 0, 0
		var x, y int32 = 0, 0

		var wg sync.WaitGroup
		wg.Add(2)

		go func() {
			defer wg.Done()

			a = 1
			x = b
		}()

		go func() {
			defer wg.Done()

			b = 1
			y = 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 := 0
	for {
		index += 1

		var a, b int32 = 0, 0
		var x, y int32 = 0, 0

		var wg sync.WaitGroup
		wg.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/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/494355.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

开源AI引擎:文本自动分类在公安及消防执法办案自动化中的应用

一、实际案例介绍 通过文本分类算法自动化处理文本数据,快速识别案件性质和关键特征,极大地提高了案件管理和分派的效率。本文将探讨这两种技术如何帮助执法机构优化资源分配,确保案件得到及时而恰当的处理,并增强公共安全管理的…

“约瑟夫环”问题的四种方法及详解注释(c++或c语言实现)

Ⅰ.故事背景 据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈&…

笔记本作为其他主机显示屏(HDMI采集器)

前言: 我打算打笔记本作为显示屏来用,连上工控机,这不是贼方便吗 操作: 一、必需品 HDMI采集器一个 可以去绿联买一个,便宜的就行,我的大概就长这样 win10下载 PotPlayer 软件 下载链接:h…

VTK 示例 基本的流程-事件交互、球体、

流程可以总结如下: 导入所需的头文件: 首先,导入了一系列 VTK 头文件,这些文件包含了所需的类和函数声明。 创建对象: 创建了两个球体(一个较大,一个较小),一个平面&…

JS-16-标签函数

一、模版字符串 模版字符串,可以非常方便地引用变量,并合并出最终的字符串。 它允许你嵌入表达式,并通过${expression}语法来执行这些表达式。模板字符串使用反引号()而不是普通的单引号或双引号。 模板字符串有几个…

【git】git使用手册

目录 一 初始化 1.1 账号配置 1.2 ssh生成 1.2.1 配置ssh 1.2.2 测试SSH 1.3 初始化本地仓库并关联远程仓库 二 使用 2.1 上传 2.2 拉取 三 问题 3.1 关联失败 一 初始化 git的安装很简单,下载后大部分进行下一步完成即可----->地址: git工具下载 1.1 账号配置…

HCIP —— 链路聚合

链路聚合 背景 随着网络规模不断扩大,用户对骨干链路的带宽和可常性提出越来越高的要求,在传统技术中,常用更换高速率的设备的方式来增加带宽,但这种方案需要付出高额的费用,而且不够灵活。 而采用链路聚合技术可以在…

记录关于智能家居的路程的一个bug___Segmentation fault(段错误)

前言 其实发生段错误的情况有很多: 其实在项目的开发中最有可能的错误就是①和②,考虑到本项目数组用的比较少,所以主要是考虑错误①指针的误用。 有时候错误就是那么离谱,声音也算是一种设备??&#xff…

Dockerfile:自定义镜像

Dockerfile 是一个文本文件,其中包含了一系列用于自动化构建Docker镜像的指令。通过编写Dockerfile,开发者能够明确地定义一个软件应用及其运行环境应该如何被封装进一个可移植、可重复构建的Docker镜像中。 第一步:在/tmp文件下新建docker…

GEE:将分类特征和标签提取到样本点,并以(csv/shp格式)下载到本地

作者:CSDN @ _养乐多_ 本文将介绍在Google Earth Engine(GEE)平台上,下载用于机器学习分类或者回归的样本点数据,样本点数据携带了分类特征和标签信息,可以以csv格式或者SHP格式。 结果如下图所示, 文章目录 一、核心函数1.1 采样1.2 下载函数二、代码链接三、完整代码…

Machine Learning机器学习之K近邻算法(K-Nearest Neighbors,KNN)

目录 前言 背景介绍: 思想: 原理: KNN算法关键问题 一、构建KNN算法 总结: 博主介绍:✌专注于前后端、机器学习、人工智能应用领域开发的优质创作者、秉着互联网精神开源贡献精神,答疑解惑、坚持优质作品共…

Python入门练习 - 学生管理系统

Python 实现读书管理系统 """ 实现一个命令行版的读书管理系统 """ import os.path import sys# 使用这个全局变量,来管理所有的学生信息 # 这个列表的每个元素都是一个‘字典’,每 个 字典就分别表示了一个同学students …

电脑访问网页获取路由器WAN口内网IP

因为运维过程中容易出现路由器配置了固定IP但是没人知道后台密码,不确定这个办公室的IP地址,且使用tracert路由追踪也只会出现路由器的LAN口网关并不会出现WAN口IP。 今日正好遇到了个好方法,经过测试可以正常使用。 方法如下: 内…

机器视觉矿山安全生产风险预警系统

一、简介 十四五规划和2035年远景目标纲要针对企业安全生产提出了多项要求。其中,提高安全生产水平要求完善和贯彻执行安全生产责任制,建立公共安全隐患排查和安全预防控制体系,要求将安全生产提升至预防和控制阶段。 目前,矿山…

0DAY漏洞是什么,如何进行有效的防护

零日漏洞,指的是软件或系统中未被公开的、未被厂商知晓的安全漏洞。这些漏洞未被修复,因此黑客可以利用它们进行攻击,而受害者往往无法防范。由于这些漏洞的存在时间很短,因此称之为“零日漏洞”,也称为“0day漏洞”。…

LeetCode:1319. 连通网络的操作次数(并查集 Java)

目录 1319. 连通网络的操作次数 题目描述: 实现代码与解析: 并查集 原理思路: 1319. 连通网络的操作次数 题目描述: 用以太网线缆将 n 台计算机连接成一个网络,计算机的编号从 0 到 n-1。线缆用 connections 表示…

【Bug-ModuleNotFoundError: No module named ‘models‘】

🚀 作者 :“码上有前” 🚀 文章简介 :Python 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 出现这个错误: 出现了ModuleNotFoundError: No module named models’的问题。 文件在Model…

春秋云境CVE-2023-27179

简介 GDidees CMS v3.9.1及更低版本被发现存在本地文件泄露漏洞,漏洞通过位于 /_admin/imgdownload.php 的 filename 参数进行利用。 正文 进入靶场发现没有什么可以利用的地方,那么就按照靶场提示来,直接访问/_admin/imgdownload.php 打开…

SQLite数据库浏览器sqlite-web

什么是 sqlite-web ? sqlite-web是一个用 Python 编写的基于 Web 的 SQLite 数据库浏览器。 软件特点: 可与您现有的 SQLite 数据库配合使用,也可用于创建新数据库。添加或删除: 表格列(支持旧版本的 SQLite&#xff…

网络链路层之(1)基础概念

网络链路层之(1)基础概念 Author: Once Day Date: 2024年3月27日 一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦… 漫漫长路,有人对你微笑过嘛… 全系列文章可参考专栏: 通信网络技术_Once-Day的博客-CSD…