go map 并发写的问题 - V2EX
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 6398 次点击
    这是一个创建于 1803 天前的主题,其中的信息可能已经有所发展或是发生改变。

    场景是多个 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  
    PRO
       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  
    PRO
       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     Solana     2540 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 13:45 PVG 21:45 LAX 05:45 JFK 08:45
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86