Golang Go语言 map 并发读写竞争问题
Golang Go语言 map 并发读写竞争问题
type IdService struct {
area int64
node int64
apply map[string]*apply
}
map 并发读写会产生数据竞争,但如果 value 是一个指针,只修改指针对象内的元素,还会有数据竞争问题吗?
不会引发 panic ,但是这个 value 对应的数据,其它 goroutine 读到的不一定是最新的。
map 本身又没有发生增减的情况,这种场景下,你用 map 图了个啥?
更多关于Golang Go语言 map 并发读写竞争问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
牵扯到多线程就用 sync.Map ,不要自己处理,手动加锁的话性能也会慢很多
sync.Map 这个适用于读多写少的情况,如果是写多读少的话 自己加锁 可能会更高。当然,绝大多数并发 情况下, 无脑 sync.map 就好了
go map 的并发读写不会 panic ,而是直接调用 throw() 函数,导致程序退出,因此,如果程序中有可能出现 map 并发读写的情况,一定要处理掉,因为这种错误出现时,程序必然挂掉。
上述的结构是会出现问题的,map 并发读写的检查大概是:当读取某个 key 时会判断一个是否有协程在写的变量,如果有协程在写,则程序退出。
测试代码:go<br>package main<br><br>import "sync"<br><br>type Person struct {<br> Name string<br>}<br><br>func main() {<br> m := make(map[string]*Person)<br> wg := sync.WaitGroup{}<br> for i := 0; i < 100; i++ {<br> wg.Add(1)<br> go func() {<br> defer wg.Done()<br> for i := 0; i < 1000; i++ {<br> m["1"] = new(Person)<br> }<br> }()<br> }<br> wg.Wait()<br>}<br>
报错信息:<br>fatal error: concurrent map writes<br><br>goroutine 21 [running]:<br>runtime.throw(0x1076c2d, 0x15)<br> /usr/local/go/src/runtime/panic.go:1117 +0x72 fp=0xc00002ff08 sp=0xc00002fed8 pc=0x102dd12<br>runtime.mapassign_faststr(0x1068c80, 0xc000098000, 0x1075205, 0x1, 0xc000056088)<br> /usr/local/go/src/runtime/map_faststr.go:211 +0x3f1 fp=0xc00002ff70 sp=0xc00002ff08 pc=0x100ea91<br>main.main.func1(0xc00009a000, 0xc000098000)<br> /Users/maning/go/tmp/hex.go:17 +0xac fp=0xc00002ffd0 sp=0xc00002ff70 pc=0x105f3ec<br>runtime.goexit()<br> /usr/local/go/src/runtime/asm_amd64.s:1371 +0x1 fp=0xc00002ffd8 sp=0xc00002ffd0 pc=0x105bb81<br>created by main.main<br> /Users/maning/go/tmp/hex.go:14 +0x91<br><br>
看错题了!尴尬
只修改指针对象内的元素,不会有数据竞争问题 (并发读写竞争), 会有数据错误问题 (并发安全问题).
前者着重于程序是否可以正常 run, 后者着重于数据是否可以准确的在多线程中 share.
只要是在 多线程, 读写场景, 就要考虑加锁, 或者采用原子操作等方式, 来进行 线程间 同步.
「修改指针对象内的元素」这里的 map 不存在 data race ,但其他地方不好说。例如指针并发读写请用 https://pkg.go.dev/sync/atomic#Value ,其他拿不准的情况把 https://golang.org/doc/articles/race_detector 打开看看。
只修改指针对象内的元素相当于多线程持有一个指针,并一起修改对应对象,每次修改不是原子的话,可能读到的是中间状态,即使修改是原子的,也有可能读到非预期的值。
了解了,感谢解答!
在Go语言中,map 类型并不是线程安全的,因此在并发读写时会出现竞争问题。这可能导致程序崩溃、数据不一致或其他未定义行为。为了解决这个问题,有几种常见的策略:
-
使用互斥锁(Mutex): 通过
sync.Mutex
或sync.RWMutex
来保护对 map 的访问。RWMutex
提供了读锁和写锁,允许多个读操作并发进行,但写操作是独占的。这样可以确保在读写操作发生时,不会有其他操作干扰。 -
使用
sync.Map
: Go 标准库中的sync.Map
是一个并发安全的 map 实现。它内部使用了更复杂的机制来优化并发读写性能,适用于高并发场景。使用sync.Map
可以简化代码,避免手动管理锁。 -
避免并发访问: 如果可能,重新设计数据结构或算法,以避免对 map 的并发访问。例如,可以使用 channel 来传递数据,或者将 map 拆分成多个小 map,每个 map 由单独的 goroutine 管理。
-
使用原子操作: 对于简单的计数器或标志位等场景,可以使用
sync/atomic
包中的原子操作来实现线程安全,但这通常不适用于复杂的 map 操作。
在实际应用中,选择哪种方法取决于具体的使用场景和需求。对于大多数并发读写 map 的场景,使用 sync.Map
或互斥锁是较为常见且有效的解决方案。