1

go 协程,数据竞争及对应解决方法

 1 year ago
source link: https://hxd.life/2023/05/04/go-%E5%8D%8F%E7%A8%8B-%E6%95%B0%E6%8D%AE%E7%AB%9E%E4%BA%89%E5%8F%8A%E5%AF%B9%E5%BA%94%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

go 协程,数据竞争及对应解决方法

go 协程,数据竞争及对应解决方法

author: he xiaodong date: 2023-05-04

有个查询结果集的操作,无可避免的需要在循环获取数据,然后将结果集放到 map 中,这个操作在压测的时候,没出现问题,发布到生产环境之后,开始偶现 fatal error: concurrent map read and map write 错误,导致容器重启了。

多个协程同时对 map 进行读写操作,导致数据竞争 测试环境压测未复现是因为单个 pod 常规时间只有一个 CPU,资源不够用了才会使用两个 CPU,单核的情况下,协程是串行执行的,所以没有出现数据竞争的问题。 同时也没开着数据竞争检测,也没有检测出来这个问题

在本机多核CPU情况下,执行 go run --race main.go 启动项目,调用方法,会有提示 data race,再开始对应解决问题。 出现数据竞争告警时,就是需要解决问题,无则是其他情况

==================
WARNING: DATA RACE
Write at 0x00c00008e000 by goroutine 7:
 main.main.func2()
Previous write at 0x00c00008e000 by goroutine 6:
 main.main.func1()
==================

① 使用 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 简单易理解

数据竞争 (data race) 的发生条件是:当多个协程同时访问一个相同内存地址,并且至少有一个在进行写操作时,数据竞争意味着不确定的行为。 而不存在数据竞争不代表结果就是确定的。实际上,一个应用程序即使不存在数据竞争,但它的行为肯依赖于不可控的发生时间或执行顺序,这就是竞争条件 (race condition)

参考链接:

  1. 深度解密go之 sync.Map
  2. 【Go基础篇】 彻底搞懂 RWMutex 实现原理
  3. Go语言并发–传统锁与channel的选择
  4. [Go 并发 数据竞争及竞争条件](https://blog.csdn.net/weixin_41335923/article/details/124266638)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK