V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
yujianwjj
V2EX  ›  Go 编程语言

go map 并发写的问题

  •  
  •   yujianwjj · 2021-01-25 14:07:39 +08:00 · 5950 次点击
    这是一个创建于 1405 天前的主题,其中的信息可能已经有所发展或是发生改变。

    场景是多个 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
    

    现在有两个问题:

    1. 为什么 count 为 5000 就报错,100 的时候不报错。
    2. 多个 goroutine 对 map 只写不读的场景有什么效率更高的方式。
    30 条回复    2021-01-27 14:14:22 +08:00
    YUX
        1
    YUX  
       2021-01-25 14:12:58 +08:00
    用 sync.Map
    Takamine
        2
    Takamine  
       2021-01-25 14:18:04 +08:00 via Android
    5000 报错,100 不报错就是单纯概率问题吧。

    只写不读可以给 map 包一层方法,在写的地方加锁。
    BeautifulSoap
        3
    BeautifulSoap  
       2021-01-25 14:18:28 +08:00 via Android
    因为你 count 5000 的时候触发 map 同时写入的几率非常高啊。。。

    100 一下子就执行完毕了数量也少同时写入几率小
    kiddingU
        4
    kiddingU  
       2021-01-25 14:19:36 +08:00   ❤️ 1
    @YUX 看楼主的场景是只写不读,sync.Map 不适合这种场景,锁的粒度太大, 用 concurrent map 就行,或者自己写一个算法,减小锁的粒度
    MidGap
        5
    MidGap  
       2021-01-25 14:20:12 +08:00   ❤️ 1
    哈哈哈哈哈好可爱
    JKeita
        6
    JKeita  
       2021-01-25 14:20:23 +08:00
    我这 10 都报错,可能跟电脑 CPU 性能有关吧,把数据先并发入 chan,再按顺序读取写入 map ?
    monsterxx03
        7
    monsterxx03  
       2021-01-25 14:23:00 +08:00
    100 的时候你多试几次就挂了,或者加 -race.
    sync.Map 只对读多写少的场景有效率提升.
    单 map 每次写加锁可能还不如顺序写.
    一般优化思路是做 sharding, 比如预先分配 8 个 map, 每次写的时候 i%8 决定写入哪个 map
    capti0n
        8
    capti0n  
       2021-01-25 14:23:27 +08:00
    个人理解:
    1.golang 的 map 是 hashmap,会默认分配一部分桶出来,这时并行写入或者访问没问题,
    当超过一定阈值,会触发扩容,这时就会有并发问题。
    2.sync.map 有试过吗?
    yujianwjj
        9
    yujianwjj  
    OP
       2021-01-25 14:25:57 +08:00
    @kiddingU 你说的 concurrent map 是这个吗? https://github.com/orcaman/concurrent-map
    Jooooooooo
        10
    Jooooooooo  
       2021-01-25 14:28:22 +08:00
    第一个疑问再次说明并发 bug 很难发现.
    kiddingU
        11
    kiddingU  
       2021-01-25 14:39:26 +08:00
    @yujianwjj 是的,也是加锁,只不过是对锁进行了 shard,减轻锁的粒度
    YUX
        12
    YUX  
       2021-01-25 14:42:37 +08:00 via iPhone
    @kiddingU 好的 学习了
    cloverstd
        13
    cloverstd  
       2021-01-25 16:22:47 +08:00
    比较好奇😯,什么业务场景下是只写不读的
    如果只写,是不是可以考虑换个 free-lock 的数据结构来存
    joesonw
        14
    joesonw  
       2021-01-25 17:04:49 +08:00   ❤️ 1
    nuk
        15
    nuk  
       2021-01-25 17:26:39 +08:00
    因为加的越多 hash 冲突就越多,添加一个键花的时间就越久,超过了启动一个 goroutine 的时间,就会报错了。
    用无锁队列然后单线程写好一点吧,写 map 应该不是瓶颈
    ihipop
        16
    ihipop  
       2021-01-25 19:47:30 +08:00 via Android
    @nuk 弄个 chanel 单向灌进去就行吧。。
    nuk
        17
    nuk  
       2021-01-25 20:45:57 +08:00
    @ihipop channel 有锁的呀,太多 goroutine 写就不行了
    YouLMAO
        18
    YouLMAO  
       2021-01-25 21:35:18 +08:00
    楼主是只写不读, 必须用 mutex, 连 rwmutex 都不要, 必须比 sync.Map 性能好, 我说的, 性能经过 G 家认证
    raaaaaar
        19
    raaaaaar  
       2021-01-25 23:02:00 +08:00 via Android
    为什么只写不读?只写的的话那些数据有什么用,是什么业务场景啊
    felixin
        20
    felixin  
       2021-01-25 23:38:06 +08:00 via Android
    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
    xmge
        21
    xmge  
       2021-01-25 23:59:29 +08:00
    1. 概率问题,对共享资源同时操作肯定会报错
    2. 如果是只读不写,只能加锁,sync.map 也不要用,sync.map 底层是读写分离,写时加锁。
    yzbythesea
        22
    yzbythesea  
       2021-01-26 02:49:07 +08:00
    chan or mutex lock
    Kinnice
        23
    Kinnice  
       2021-01-26 09:53:20 +08:00
    你电脑性能有点好
    yujianwjj
        24
    yujianwjj  
    OP
       2021-01-26 10:22:28 +08:00
    抱歉,题目描述有误,我的场景是先写后读,先加载大量的数据到 map 里面,后面再查找。
    march1993
        25
    march1993  
       2021-01-26 11:23:34 +08:00
    用 goroutine+chan 啊,一个 routine 专门读 chan 然后修改 map,其他 routine 把要修改的内容发到 chan 里
    sunshinev
        26
    sunshinev  
       2021-01-26 11:36:26 +08:00
    sync.Map
    mengdodo
        27
    mengdodo  
       2021-01-26 15:07:01 +08:00
    我记得当初看到过这样一句话:Go 中的 Map 类型不是一种安全的数据类型。所以我比较菜,直接写到 redis 中
    kiddingU
        28
    kiddingU  
       2021-01-26 15:58:51 +08:00
    说用 chan 的,chan 底层数据结构是个啥,有研究过吗~
    Dongxiem
        29
    Dongxiem  
       2021-01-27 12:57:09 +08:00
    @kiddingU 这个很难说的清楚的吧?可以看看这个 深度解密 Go 语言之 channel ( https://zhuanlan.zhihu.com/p/74613114
    kiddingU
        30
    kiddingU  
       2021-01-27 14:14:22 +08:00
    有啥难说清楚的,看源码不就清楚了~
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2763 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 09:32 · PVG 17:32 · LAX 01:32 · JFK 04:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.