golang 记一次data race排查过程
golang 记一次data race排查过程
- 背景
- data race 现场
- 解决思路
- 经验总结
data race在写并发代码时候经常遇到,相关基础概念的介绍可以参考之前一篇文章:golang data race 竞态条件https://louyuting.blog.csdn.net/article/details/103606831
这篇文章主要是记录项目中一次 data race的排查过程。
背景
大概背景是这样的,最近项目中需要在golang中实现一个并发安全的list容器,list容器的长度不一定,通过在初始化的时候指定,容器必须要能够并发安全的更新容器内的每一个元素。经过调研,决定采用类似于 Java 中的 AtomicReferenceArray
的实现方案。关于AtomicReferenceArray
的实现原理这里就不细说了,网上很多博客介绍。
需求大概是这样,容器里面保存的是一个指针列表,每一个指针指向一个统计窗口的数据结构的地址。因为golang中的数组申明时候必须指定长度,不指定长度默认就是slice,所以也就是借助于slice来实现数组的目的。大概结构如下图:
里面的W0 - W(n-1) 都是指针,指针的size是8bytes(64位机器),指针分别指向一个实际的存储实体(用红色的圆来表示)。base指向数组的首地址,length表示数组的长度。
红色实体的数据结构大概是这样:
type bucket struct {startTime uint64value interface{}
}
需要实现的方法也比较简单,基本就是原子操作的常规套路, get
和 compareAndSet
,最初版本源码大概是这样:
//该函数获取第 idx 个元素的地址
func (a *atomicPointerArray) offset(idx int) uintptr {......//省略
}func (a *atomicPointerArray) get(idx int) *bucket {// data race pointbucketPtr := *(**bucket)(unsafe.Pointer(a.offset(idx)))bucketPointer := unsafe.Pointer(bucketPtr)return (*bucket)atomic.LoadPointer(&bucketPointer)
}func (a *atomicPointerArray) compareAndSet(idx int, except, update *bucket) bool {offset := (**bucket)(unsafe.Pointer(a.offset(idx)))bucketPointer := (*unsafe.Pointer)unsafe.Pointer(offset)// data race pointreturn atomic.CompareAndSwapPointer(bucketPointer,unsafe.Pointer(except),unsafe.Pointer(update))
}
实际调用的代码就不给出了,大概就是存在多协程并发的调用 get
和 compareAndSet
方法。
data race 现场
如前面所描述,我用 go test -v -race ./...
来跑data race的时候就爆出了数据竞争,显示 get
函数的第一行和compareAndSet
函数的第三行存在data race。
这个问题很隐蔽…
get
函数的第一行:bucketPtr实际上第 idx 个元素,也就是一个 *bucket 指针,这里产生了一个对第idx个元素的读操作,但是这个读不是原子的,这里不仔细分析,很难分得清楚。
compareAndSet
函数的第三行:这里很简单,就是一个对第idx个元素写操作,写是原子的。
所以也就是多协程的一个并发读写问题。
解决思路
定位了问题解决起来就很简单了,把 get
函数的第一行的读取操作去掉也就OK了,顺便简化下代码:
func (a *atomicPointerArray) get(idx int) *bucket {return (*bucket)(atomic.LoadPointer((*unsafe.Pointer)(a.elementOffset(idx))))
}func (a *atomicPointerArray) compareAndSet(idx int, except, update *bucket) bool {return atomic.CompareAndSwapPointer((*unsafe.Pointer)(aa.elementOffset(idx)), unsafe.Pointer(except), unsafe.Pointer(update))
}
这样就get
函数避免了一次对 *bucket 的读操作。
经验总结
从这个case里面还是有一些经验可以总结:
- 确定好哪些对象是并发访问的,对于并发访问的对象一定要做好读写分析;
- 不止是对象的读写分析,还有对象里面的属性如果存在并发读写访问也要做并发控制;
- 对于 指针 类型的变量,涉及到 unsafe.Pointer 时候一定要谨慎,任何非类型安全的指针转换成(*Obj)都是读操作,谨慎谨慎。
- 不要用 uintptr 做指针变量的访问,uintptr只用作地址数学运算,并且都放在一行内执行,避免野指针。
golang 记一次data race排查过程相关推荐
- 记一次网站故障排查过程(nginx 504状态码、 upstream timed out (110: Connection timed out)以及jbd2引起IO高
一.问题描述 客户侧反馈无法正常访问系统,页面转圈,时好时坏,访问不稳定. 二.系统环境: 机器环境:UOS . nginx .php(对接其他服务器kingbase .钉钉.redis .KF) E ...
- 记一次OOM问题排查过程
上周运维反馈线上程序出现了OOM,程序日志中的输出为 Exception in thread "http-nio-8080-exec-1027" java.lang.OutOfMe ...
- 记一次Kafka warning排查过程
1.前因 在配合测试某个需求的时候,正好看到控制台打印了个报错,如下: 2023-03-06 17:05:58,565[325651ms][pool-28-thread-1][org.apache.k ...
- 记一次SOFA内存泄漏排查过程
记一次内存泄漏排查过程 起因 某天中午大家还在安静的午休,睡得正香的时候突然被一阵手机滴-滴滴直响短信惊醒.一看是应用的服务器告警并且对应服务的所有机器都在告警"健康检查失败,自动拉下线&q ...
- golang的 data race 分析
golang的data race 一.名词解析 1.data race: Any race is a bug 定义: ①多个线程(协程)对于同一个变量.②同时地.③进行读/写操作.并且④至少有一个线程 ...
- golang data race 竞态条件
golang race condition 竞态条件 data race race condition golang race detector golang的协程机制使得编写并发代码变得非常容易,但 ...
- 多队列 部分队列没有包_记一次TCP全队列溢出问题排查过程
简介:记一次TCP全队列溢出问题排查过程 1. 前言 本文排查的问题是经典的TCP队列溢出问题,因TCP队列问题在操作系统层面没有明显的指标异常,容易被忽略,故把排查过程分享给大家. 2. 问题描述 ...
- 记一次线上服务假死排查过程
大家好,我是烤鸭: 最近线上问题有点多啊,分享一个服务假死的排查过程. 问题描述 9点10分,收到进程无响应报警(一共6台机器,有1台出现),后来又有1台出现. 排查思路 首先确认是否误报或者网络抖动 ...
- 记一次线上cpu飙升100%的排查过程
大家好,我是烤鸭: 最近没怎么写技术文章,还是得回归下初心,正好前几天出现个线上问题,记录下排查过程. 问题描述 某个时间点,接收到接口响应慢报警. 过一会收到服务器cpu可用率低(<10%)报 ...
最新文章
- 开发日记-20190802 关键词 闲聊
- JAVA中类的访问修饰符的作用范围
- 安装gcc 4.8.2 for cxx 11
- Nginx之gzip压缩配置
- 如何解析一个字符串并返回一个嵌套数组?
- 有关Silverlight3.0在浏览器外运行的问题
- find结合rm删除或mv移动文件的方法
- 开课吧课堂之未被捕获的异常
- retorfit converter使用说明
- Lucene.Net
- fpga与海思BT1120调试问题记录
- 遗传算法bp神经网络原理,bp神经网络 遗传算法
- oracle拆分分区语法详解大全_Oracle分区表详解
- C#语言入门详解(刘铁锰)---泛型
- c语言井号花括号怎么打,大括号怎么打,手把手教你word大括号怎么输入
- uniapp扫描二维码问题
- 2012-8-18可樂美文分享《遗留在时…
- OS第七章 文件管理
- 小试debian-7.11.0-amd64+Plone5.1.2全文检索和预览中文WORD中文PDF
- GBK 和 UTF8编码