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

go 语言有没有线程安全的数据类型?

  •  1
     
  •   hkhk366 · 2022-06-04 04:37:55 +08:00 · 5962 次点击
    这是一个创建于 938 天前的主题,其中的信息可能已经有所发展或是发生改变。

    go 语言中都是可以并行的协程,我们为了简单下面统一称为线程安全。

    slice 和 map 都是线程不安全的,我知道 sync.map 是安全的,但是一是操作不方便比如下面这一段

    	a := sync.Map{}
    	a.Store("1", 1)
    

    就得用 store 方法才能存储,而普通 map 可以直接 map["key"]操作,这个小问题还可以勉强接受。

    最关键的问题是只提供了线程安全的 sync.map 这种,没有提供线程安全的 slice ,如果要自己一个一个加锁或者用 channel 控制非常麻烦。请问有没有别人实现好的线程安全的 slice 或者线程安全的全部数据结构 ?

    我在 github 上搜全都是别人实现的线程安全的 map 或者 queue 或者 set ,而且风格非常不统一,求有哪位大神实现了线程安全的 slice,map,queue,stack 等常见数据结构,这样风格能统一,并且还能线程安全,谢谢各位。

    我实在技术不太行,我写的话经常有并发问题,所以才来求这种线程安全的基础数据结构,我知如果用了这种线程安全的基础数据结构会导致性能下降,没关系,我的系统对性能要求没那么高。我要是有能力一个人实现上面这些,我就早全一个风格实现不来麻烦大家了。希望有大神能帮帮我,万分感谢。

    第 1 条附言  ·  2022-06-04 07:42:51 +08:00
    下面有人提到 chan 就挺安全,我不明白他的意思,或许他是指用 chan 代替 slice?如果是这个意思,恐怕不行,因为 chan 只有相当于 pushright 和 popleft ,无法模拟 slice 的按下表找元素,而且 chan 不能临时增加容量。如果他是指用 chan 进行通讯然后坐到线程安全,我能找到的 slice 线程安全的代码都只有加入元素安全,还没有支持 slice 的其他操作比如按下标查找,也没有 slice 的切片等更高级的操作,我希望的是一个完整的 slice 功能,谢谢。
    22 条回复    2022-06-08 14:04:55 +08:00
    c8iter
        1
    c8iter  
       2022-06-04 05:41:31 +08:00
    chan 就挺安全的
    yulon
        2
    yulon  
       2022-06-04 07:48:25 +08:00
    线程安全的数据类型,只能保证单条操作是原子的,不能保证逻辑流程也是线程安全的,不仅仅是性能的问题。

    sync 包很简陋,因为这是不被推荐的方式,只能适用于某些情况,并不能一把梭。

    多线程编程本来就是很考验逻辑的,建议用 JS 。
    fds
        3
    fds  
       2022-06-04 08:13:04 +08:00
    用 chan 的意思是,把对同一 slice 的操作控制在一个 goroutine 里,这样就不用考虑锁的问题。
    没有现成的是因为锁的需求场景都不一样。比如有两个 slice ,你可以用同一个锁限制他们的访问。拿到锁了,就可以对 slice 做很多放多操作。但是如果是别人包装好的,为了保证不出错,可能每次查询都要锁一次,效率太低。
    Yvette
        4
    Yvette  
       2022-06-04 09:15:50 +08:00
    好久没写 Go 了,隐约记得之前解决这种问题都直接加一个 sync.Mutex ,非常粗暴但有效而且易懂
    gogorush
        5
    gogorush  
       2022-06-04 09:47:59 +08:00
    Java 和 golang 的编程逻辑其实有蛮多不一样的地方 很多用这么多锁的地方感觉都在 golang 里面用起来怪怪的 这也是雪心新语言的一个必经之路吧 如果全用老的思维来处理问题 新语言的特性你也用不上了 那干嘛不直接用 Java 呢
    iosyyy
        6
    iosyyy  
       2022-06-04 10:42:30 +08:00
    @fds 直接用 copy on write 的思路不就行了吗..扯这么多不还是因为没有现成的实现吗..
    keakon
        7
    keakon  
       2022-06-04 10:49:52 +08:00
    你自己加个锁不就行了吗? sync.Map 单独实现出来是因为可以分片加锁,这样不同的 cpu 访问不同的分片时可以不用加锁,而其他数据结构没有这样的优化方法。
    qwqaq
        8
    qwqaq  
       2022-06-04 11:05:11 +08:00 via iPhone
    最近用到的一些 Golang 处理并发问题的方法:sync.Mutex 加互斥锁保证同一个时间点只有一个操作在进行;使用 sync.Once 初始化单个实例(例如懒汉式单例模式);使用 signleflight.Group 解决并发缓存查询问题(缓存击穿问题)
    stevefan1999
        9
    stevefan1999  
       2022-06-04 11:43:45 +08:00 via Android
    stevefan1999
        10
    stevefan1999  
       2022-06-04 11:47:11 +08:00 via Android
    iyaozhen
        11
    iyaozhen  
       2022-06-04 15:34:21 +08:00
    其实吧 你自己说技术一般,那就不要多协程操作呀。或者是不要多协程操作一个 slice
    ch2
        12
    ch2  
       2022-06-04 18:51:40 +08:00
    chan 保平安,尽可能不要用锁,实在不行 sync.map 肯定够用了
    george404
        13
    george404  
       2022-06-04 19:17:56 +08:00
    如果是用 slice 这些,如果是复杂的用法,你可以封成一个对象来访问啊。对象里面添加 lock 。这样你就不要每个调用的地方都放一个 RWlock 这些。
    Binb
        14
    Binb  
       2022-06-04 20:24:22 +08:00
    说的挺全了:锁、chan 、sync.Map, 还是不要并发好点,简单。
    fds
        15
    fds  
       2022-06-04 22:37:19 +08:00
    @iosyyy 我说的 chan 或者锁,两者都跟 copy on write 没有关系。之所以 go 官方没有楼主说的功能,是因为不需要,用别的思路不需要 slice 本身线程安全。天下没有免费的午餐,保证线程安全是要有代价的,go 希望开发者自己选择承担多少锁的时间,而不是直接无脑使用一个库。
    iosyyy
        16
    iosyyy  
       2022-06-04 23:48:37 +08:00
    @fds 所以 go 语言本身使用协程希望并发都在用户态简单 然后不加一个很容易封装的 slice 说希望开发者选择吗? 站不住脚吧 同样的我举 copy on write 是指的 go 语言完全可以封装一个这种思路的对象然后 open 出来哪怕这东西很不自由 但是他很有用
    iosyyy
        17
    iosyyy  
       2022-06-04 23:49:31 +08:00
    @fds 如果想不无脑建议让 go 把线程放出来 不要使用协程这种无脑的方式
    fds
        18
    fds  
       2022-06-05 09:20:28 +08:00
    @iosyyy 哦,你是说 copy on write 是另一种思路,我之前没理解你的回复。copy on write 明显更难实现一些,成本更高一些。go 的设计理论是用 CSP ,就是把对同一变量的同一时间的操作限制在同一协程里,用 chan 在协程间传递命令或结果,这样对所有变量(不仅仅是 slice ,最普通的变量都不应该同时被多处读写)操作前都不用考虑锁的问题。如果是初学 go ,建议尽量往这个思路上靠拢,因为理论上是完备的,所有问题肯定是可以通过这种思路解决的。但是 go 不像一些更前卫的语言直接封掉了协程间共享变量,还是保留了锁,就是在一些特定情景下,直接用锁更直观。这就跟 go 保留了 goto 语句一样,你总是可以用别的方法避免 goto 的,但有时就是直接 goto 更清晰明了。像 sync.Map 这种封装是直到 1.9 才放出,就是因为官方认为这并不是必要的。现实总是充满各种妥协,官方为了避免一些用户自己实现出问题,还是集成了 sync.Map ,在 https://pkg.go.dev/sync#Map 文档里说了,只在特定场景中使用。

    你后面说的线程我不太理解,协程已经把线程池这个概念封装掉了,不用让每个程序员都直接处理一些底层优化。不清楚你说的无脑是什么意思。
    statumer
        19
    statumer  
       2022-06-05 12:34:36 +08:00 via iPhone
    你自己加个 mutex 不就好了,数组这个数据结构太灵活了,如果你搞不懂 mutex 的话其实很容易出错。比如说你获取 index 然后删除应不应该是原子操作呢? shift 应不应该是原子操作呢?
    alexmy
        20
    alexmy  
       2022-06-05 22:48:00 +08:00
    Actor 模式保命?看看这个。
    hailaz
        21
    hailaz  
       2022-06-06 10:20:57 +08:00
    lysS
        22
    lysS  
       2022-06-08 14:04:55 +08:00
    不要通过共享内存来通信
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   936 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 21:05 · PVG 05:05 · LAX 13:05 · JFK 16:05
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.