Golang Go语言中map并发写的问题
Golang Go语言中map并发写的问题
场景是多个 goroutine 对一个 map 只写不读。
最开始用加锁的方式,来实现多个 goroutine 对一个 map 进行写入。后来发现效率有点低。就尝试了下不加锁的方式。
func TestMap(t *testing.T) {
a := map[string]int{}
count := 100
wg := sync.WaitGroup{}
wg.Add(count)
for i := 0; i < count; i++ {
go func(i int) {
a[fmt.Sprintf("%d", i)] = i
wg.Done()
}(i)
}
wg.Wait()
for k, v := range a {
fmt.Println(k, " ", v)
}
}
以上测试代码能够正常工作,并且写入的数据正确,我就以为 map 只写不读的情况是可以不加锁的。
但是实际场景中 count 为 5000,然后就报错了:
fatal error: concurrent map writes
现在有两个问题:
- 为什么 count 为 5000 就报错,100 的时候不报错。
- 多个 goroutine 对 map 只写不读的场景有什么效率更高的方式。
更多关于Golang Go语言中map并发写的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
5000 报错,100 不报错就是单纯概率问题吧。
只写不读可以给 map 包一层方法,在写的地方加锁。
因为你 count 5000 的时候触发 map 同时写入的几率非常高啊。。。
100 一下子就执行完毕了数量也少同时写入几率小
看楼主的场景是只写不读,sync.Map 不适合这种场景,锁的粒度太大, 用 concurrent map 就行,或者自己写一个算法,减小锁的粒度
哈哈哈哈哈好可爱
我这 10 都报错,可能跟电脑 CPU 性能有关吧,把数据先并发入 chan,再按顺序读取写入 map ?
100 的时候你多试几次就挂了,或者加 -race.
sync.Map 只对读多写少的场景有效率提升.
单 map 每次写加锁可能还不如顺序写.
一般优化思路是做 sharding, 比如预先分配 8 个 map, 每次写的时候 i%8 决定写入哪个 map
个人理解:
1.golang 的 map 是 hashmap,会默认分配一部分桶出来,这时并行写入或者访问没问题,
当超过一定阈值,会触发扩容,这时就会有并发问题。
2.sync.map 有试过吗?
第一个疑问再次说明并发 bug 很难发现.
是的,也是加锁,只不过是对锁进行了 shard,减轻锁的粒度
好的 学习了
比较好奇😯,什么业务场景下是只写不读的
如果只写,是不是可以考虑换个 free-lock 的数据结构来存
因为加的越多 hash 冲突就越多,添加一个键花的时间就越久,超过了启动一个 goroutine 的时间,就会报错了。
用无锁队列然后单线程写好一点吧,写 map 应该不是瓶颈
弄个 chanel 单向灌进去就行吧。。
channel 有锁的呀,太多 goroutine 写就不行了
楼主是只写不读, 必须用 mutex, 连 rwmutex 都不要, 必须比 sync.Map 性能好, 我说的, 性能经过 G 家认证
为什么只写不读?只写的的话那些数据有什么用,是什么业务场景啊
If there is only one lesson I learn from the 30 years experience of network/multi-threading programming, that is NEVER SHARE STATES.
Pieter Hintjens
- 概率问题,对共享资源同时操作肯定会报错
2. 如果是只读不写,只能加锁,sync.map 也不要用,sync.map 底层是读写分离,写时加锁。
chan or mutex lock
抱歉,题目描述有误,我的场景是先写后读,先加载大量的数据到 map 里面,后面再查找。
用 goroutine+chan 啊,一个 routine 专门读 chan 然后修改 map,其他 routine 把要修改的内容发到 chan 里
sync.Map
我记得当初看到过这样一句话:Go 中的 Map 类型不是一种安全的数据类型。所以我比较菜,直接写到 redis 中
说用 chan 的,chan 底层数据结构是个啥,有研究过吗~
这个很难说的清楚的吧?可以看看这个 深度解密 Go 语言之 channel ( https://zhuanlan.zhihu.com/p/74613114 )
有啥难说清楚的,看源码不就清楚了~
在Go语言中,map
类型并不是线程安全的,这意味着如果在多个goroutine中并发地对同一个map
进行读写操作,而没有采取适当的同步措施,会导致运行时panic。
具体来说,Go的运行时检测到对map
的并发写操作时,会抛出如下错误:“fatal error: concurrent map writes”。这是因为map
的内部结构在并发修改时可能会遭到破坏,导致数据不一致或程序崩溃。
为了安全地在多个goroutine中共享map
,有几种常见的解决方案:
-
使用互斥锁(
sync.Mutex
):在访问map
的代码块周围加上锁,确保同一时间只有一个goroutine可以访问map
。 -
使用读写锁(
sync.RWMutex
):如果读操作远多于写操作,使用读写锁可以提高性能。读写锁允许多个goroutine同时进行读操作,但在写操作时,所有的读和写操作都会被阻塞。 -
使用
sync.Map
:从Go 1.9开始,标准库引入了sync.Map
,它是一个并发安全的map实现。它提供了与内置map
类似的操作,但内部实现了高效的并发控制机制。
总之,在处理Go语言中的map
并发写问题时,需要根据具体的使用场景选择合适的同步机制,以确保数据的一致性和程序的稳定性。在生产环境中,务必小心处理并发问题,以避免潜在的竞态条件和运行时错误。