golang race检查
1. 数据争用
数据争用在golang中指两个协程同时访问相同的内存空间,并且至少有一个是写操作,就是线程不安全的问题
在golang中有一种经典的数据争用错误
func save(g *data){ saveToRedis(g) } func main(){ var a map [int]data for _,k:= range a{ go save(&k) } }
可能乍一看并没有什么错误,但这其实是不安全的
在使用range时,变量k是一个堆上地址不变的对象,该地址存储的值会随着range的遍历而发生变化
如果此时我们将变量k的地址放入协程save(),已提供不阻塞的数据存储,那么最后的记过将会是后面的数据覆盖掉前面的数据,并且每一次的数据也不一定是完整的
2. race检查
golang提供了race工具来检测数据争用问题,当检测器在程序中找到数据争用时,将打印报告
该报告包含发生race冲突的协程栈以及此时正在运行的协程栈
race检查可以使用在多个go指令中:
go test -race mypkg go run -race main.go go build -race mycmd go install -race mypkg
3. race检查原理
race借助了ThreadSanitizer,这是google为了解决大量C++代码的数据争用问题而开发的一个工具,在golang中通过CGO的形式进行调用
从之前的数据争用问题可以看出,当不同的协程访问同一块内存区域并且至少有一个写时,会触发数据争用,当然也有可能不触发
3.1 矢量时钟技术
当两个协程访问同一块内存区域时,一种是协程A结束后,协程B继续执行,另一种则相反
在加互斥锁的情况下,A和B是不可能同时对一个内存区域进行写操作的,所以二者存在明显的顺序关系,这种关系就叫做happened-before
矢量时钟技术用来观察事件之间的happened-before,该技术在分布式系统中广泛应用,用于确定分布式系统中事件的因果关系,也可以用做数据争用的检测
在golang中,有n个协程就会有对应的n个逻辑时钟,而矢量时钟是所有这些逻辑时钟组成的数组,表示形式为 t=<t1, t2, t3, ...>
在下面这个例子中,有两个协程GA GB同时对一个一个变量count做加一的操作,这就是矢量时钟查看到的happened-before顺序
首先GA对变量count加互斥锁,然后对其做++操作
GB能够观察到GA对临界区加锁了,当GA释放锁时,其会更新内部对于协程GA的逻辑时钟,并增加自己的逻辑时钟,完成下一次++操作
3.2 何时触发race事件
在golang中,每个协程在创建之处都会初始化矢量时钟,并在读取或写入事件时修改自己的逻辑时钟
触发race事件的方式主要有两种
- 在golang运行时大量注入触发事件,例如在切片、数组、map、channel时
- 编译器自动插入,编译器在可能会发生数据争用的地方自动插入race相关指令
主要根据以下四点来判断是否发生数据争用:
- 是否有一个操作是写操作
- 是否接触了同一块内存
- 是否是不同的协程
- 两个事件之间是否是happened-before关系
例如当前GB的矢量时钟为<0, 1>,GA只存储了逻辑时钟2,可以被看做<2, x>,x可能是任意值,因此不能判断二者存在争用
当x > 1时,GA -> GB;当x < 1时,二者不存在任何顺序关系,证明发生了数据争用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理