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

golang 中 map 的并发问题

  •  
  •   ksedz · 2020-09-12 12:18:03 +08:00 · 3105 次点击
    这是一个创建于 1560 天前的主题,其中的信息可能已经有所发展或是发生改变。
    golang 中的 map 并发访问会报错,一般建议是使用读写锁。

    我有两个场景不太适合使用读写锁,求问有什么好的解决方案:

    1. 并发写入不同的 key,读取和写入肯定不会同时进行
    实际场景是每个协程维护一个会话信息,会话之间肯定不会冲突,会话内也不会并发读写。协程内的局部变量可以解决这个问题,但我想汇总协程内的信息,就需要使用 map 了。

    2. 大量的并发写入,少量的读取
    这个和上面是同一个业务,要对不同会话内的不同类型事件次数进行统计,然后定时打印到控制台。说这是少量的读取也不太合适,因为写入前还要查询原值,但也不是读写锁更适合的读多写少的情况。这里可能是 map 实现本身的问题了,求问有什么更优的数据结构或算法处理这一问题,可以做到高效的原子性的 increase 1 ?
    第 1 条附言  ·  2020-09-14 11:20:53 +08:00
    经试验 sync.Map 无法处理写多读少的情况,实际使用中大量的并发写操作直接把读操作卡死。这和读写锁的情况相同,都不适用场景 2

    使用了一个开源的 concurrent map 实现来解决场景 2 的问题,目前表现良好 https://github.com/orcaman/concurrent-map

    感谢各位的帮助!
    14 条回复    2020-09-12 15:42:47 +08:00
    abbycin
        1
    abbycin  
       2020-09-12 12:21:47 +08:00 via Android
    换 tbb
    ksedz
        2
    ksedz  
    OP
       2020-09-12 12:31:21 +08:00
    @abbycin 谢谢提示。搜了下,是 C++的方案?改动有点大,我先找找其他解决方案。
    jingniao
        3
    jingniao  
       2020-09-12 12:33:52 +08:00 via Android
    map 的 value 弄成结构体指针?
    qianlv7
        4
    qianlv7  
       2020-09-12 12:34:25 +08:00 via iPhone
    分桶,每个桶单独加锁
    teawithlife
        5
    teawithlife  
       2020-09-12 12:36:07 +08:00
    可以换自带的 sync.Map
    唯一坑的地方是没有 Len()方法,也就是说如果你需要获取 Map 中的元素个数,得自己遍历一遍数个数。不过看你的使用场景,应该不需要 Len()方法
    foam
        6
    foam  
       2020-09-12 12:57:27 +08:00 via Android
    第 2 个场景不要加锁了,太重。用 atomic 包的 cas,其实就是乐观锁。加上 for 重试,保证写入。
    fiypig
        7
    fiypig  
       2020-09-12 12:58:23 +08:00
    sync.Map
    fighterlyt
        8
    fighterlyt  
       2020-09-12 13:04:59 +08:00
    看来上边的人都没找到的具体的问题,map 不支持并发读写的根本原因是** 使用一个有空间大小的结构表示一个无限大小的结构**。这样必然伴随着内存的重新分配和对应的复制问题。所以不管读写是否为同一个 key,总是需要加锁。针对第二个问题,两个 map 就 ok 了,可以用同一把锁,1 个 map[string]***,1 个 map[string]int64
    wysnylc
        9
    wysnylc  
       2020-09-12 13:08:28 +08:00 via iPhone
    对着 java concurrenthashmap 实现一遍
    dongisking
        10
    dongisking  
       2020-09-12 13:24:19 +08:00 via Android
    golang 并发访问会报错?
    zhs227
        11
    zhs227  
       2020-09-12 14:24:15 +08:00
    @dongisking 准确的说是会 panic,程序直接退出。 一般用 sync.map 或者是加锁。
    reus
        12
    reus  
       2020-09-12 14:28:33 +08:00
    sync.Map 就是为这种场景定制的


    The Map type is specialized. Most code should use a plain Go map instead,
    with separate locking or coordination, for better type safety and to make it
    easier to maintain other invariants along with the map content.

    The Map type is optimized for two common use cases: (1) when the entry for a
    given key is only ever written once but read many times, as in caches that
    only grow, or (2) when multiple goroutines read, write, and overwrite
    entries for disjoint sets of keys. In these two cases, use of a Map may
    significantly reduce lock contention compared to a Go map paired with a
    separate Mutex or RWMutex.
    keepeye
        13
    keepeye  
       2020-09-12 15:29:17 +08:00
    1. 用 sync.Map
    2. 用第三方封装过的 map,分 bucket 的,但还是加锁的思路
    3. 自己封装一层,通过 channel 的去限制并发读写 map
    dafsic
        14
    dafsic  
       2020-09-12 15:42:47 +08:00 via Android
    给你看看我用的方法,不说多好,反正我一直这样用 https://www.v2ex.com/t/596606#reply18
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3461 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 10:27 · PVG 18:27 · LAX 02:27 · JFK 05:27
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.