共计 1189 个字符,预计需要花费 3 分钟才能阅读完成。
原文链接:何晓东 博客
场景
有个查问后果集的操作,无可避免的须要在循环获取数据,而后将后果集放到 map 中,这个操作在压测的时候,没呈现问题,公布到生产环境之后,开始偶现 fatal error: concurrent map read and map write
谬误,导致容器重启了。
起因
多个协程同时对 map 进行读写操作,导致数据竞争
测试环境压测未复现是因为单个 pod 惯例工夫只有一个 CPU,资源不够用了才会应用两个 CPU,单核的状况下,协程是串行执行的,所以没有呈现数据竞争的问题。
同时也没开着数据竞争检测,也没有检测进去这个问题
调试
在本机多核 CPU 状况下,执行 go run --race main.go
启动我的项目,调用办法,会有提醒 data race
,再开始对应解决问题。
解决方案
① 应用 sync.Mutex/sync.RWMutex 加锁
互斥锁:
Mutex 是互斥锁的意思,也叫排他锁,同一时刻一段代码只能被一个线程运行,应用只须要关注办法 Lock(加锁)和 Unlock(解锁)即可。
在 Lock()和 Unlock()之间的代码段称为资源的临界区(critical section),是线程平安的,任何一个工夫点都只能有一个 goroutine 执行这段区间的代码。
Mutex 在大量并发的状况下,会造成锁期待,对性能的影响比拟大。
读写锁:
读写锁的读锁能够重入,在曾经有读锁的状况下,能够任意加读锁。
在读锁没有全副解锁的状况下,写操作会阻塞直到所有读锁解锁。
写锁定的状况下,其余协程的读写都会被阻塞,直到写锁解锁。
依据业务场景,按需进行加锁,尽量减少锁的粒度,进步性能。
② 应用 sync.Map
go 原生的 map 不是线程平安的,sync.Map
是线程平安的,读取,插入,删除也都放弃着常数级的工夫复杂度。并且它通过空间换工夫的形式,应用 read 和 dirty 两个 map 来进行读写拆散,升高锁工夫来提高效率。
sync.Map
实用于读多写少的场景,如果并发写多的场景,还是须要加锁的对于写多的场景,会导致 read map 缓存生效,须要加锁,导致抵触变多;而且因为未命中 read map 次数过多,导致 dirty map 晋升为 read map,这是一个 O(N) 的操作,会进一步升高性能。
③ 应用 channel 通道传递数据
channel 是 goroutine 之间的通信形式,能够用来传递数据,也能够用来传递信号,比方完结信号,超时信号等。
go 的一个准则也是:通过通信来共享内存,而不是通过共享内存来通信。channel 也是线程平安的,能够用来解决数据竞争的问题。
额定准则: 如果有数据传递后,持续有进行解决,能够应用 channel,如果仅是赋值,无其余操作,间接加锁或者 sync.Map 简略易了解
参考链接:
- 深度解密 go 之 sync.Map
- 【Go 根底篇】彻底搞懂 RWMutex 实现原理
文章在梦康群大佬们指导 + github copilot 提醒下欠缺的。