Golang Go语言 map 并发读写竞争问题

发布于 1周前 作者 gougou168 来自 Go语言

Golang Go语言 map 并发读写竞争问题

type IdService struct {
area  int64
node  int64
apply map[string]*apply
}

map 并发读写会产生数据竞争,但如果 value 是一个指针,只修改指针对象内的元素,还会有数据竞争问题吗?

12 回复

不会引发 panic ,但是这个 value 对应的数据,其它 goroutine 读到的不一定是最新的。

map 本身又没有发生增减的情况,这种场景下,你用 map 图了个啥?

更多关于Golang Go语言 map 并发读写竞争问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


不会,不关心数据修改顺序性就没啥影响

牵扯到多线程就用 sync.Map ,不要自己处理,手动加锁的话性能也会慢很多

sync.Map 这个适用于读多写少的情况,如果是写多读少的话 自己加锁 可能会更高。当然,绝大多数并发 情况下, 无脑 sync.map 就好了

你这个 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 &lt; 100; i++ {<br> wg.Add(1)<br> go func() {<br> defer wg.Done()<br> for i := 0; i &lt; 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 类型并不是线程安全的,因此在并发读写时会出现竞争问题。这可能导致程序崩溃、数据不一致或其他未定义行为。为了解决这个问题,有几种常见的策略:

  1. 使用互斥锁(Mutex): 通过 sync.Mutexsync.RWMutex 来保护对 map 的访问。RWMutex 提供了读锁和写锁,允许多个读操作并发进行,但写操作是独占的。这样可以确保在读写操作发生时,不会有其他操作干扰。

  2. 使用 sync.Map: Go 标准库中的 sync.Map 是一个并发安全的 map 实现。它内部使用了更复杂的机制来优化并发读写性能,适用于高并发场景。使用 sync.Map 可以简化代码,避免手动管理锁。

  3. 避免并发访问: 如果可能,重新设计数据结构或算法,以避免对 map 的并发访问。例如,可以使用 channel 来传递数据,或者将 map 拆分成多个小 map,每个 map 由单独的 goroutine 管理。

  4. 使用原子操作: 对于简单的计数器或标志位等场景,可以使用 sync/atomic 包中的原子操作来实现线程安全,但这通常不适用于复杂的 map 操作。

在实际应用中,选择哪种方法取决于具体的使用场景和需求。对于大多数并发读写 map 的场景,使用 sync.Map 或互斥锁是较为常见且有效的解决方案。

回到顶部