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

2022-06-04 04:37:55 +08:00
 hkhk366

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 等常见数据结构,这样风格能统一,并且还能线程安全,谢谢各位。

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

5920 次点击
所在节点    Go 编程语言
22 条回复
c8iter
2022-06-04 05:41:31 +08:00
chan 就挺安全的
yulon
2022-06-04 07:48:25 +08:00
线程安全的数据类型,只能保证单条操作是原子的,不能保证逻辑流程也是线程安全的,不仅仅是性能的问题。

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

多线程编程本来就是很考验逻辑的,建议用 JS 。
fds
2022-06-04 08:13:04 +08:00
用 chan 的意思是,把对同一 slice 的操作控制在一个 goroutine 里,这样就不用考虑锁的问题。
没有现成的是因为锁的需求场景都不一样。比如有两个 slice ,你可以用同一个锁限制他们的访问。拿到锁了,就可以对 slice 做很多放多操作。但是如果是别人包装好的,为了保证不出错,可能每次查询都要锁一次,效率太低。
Yvette
2022-06-04 09:15:50 +08:00
好久没写 Go 了,隐约记得之前解决这种问题都直接加一个 sync.Mutex ,非常粗暴但有效而且易懂
gogorush
2022-06-04 09:47:59 +08:00
Java 和 golang 的编程逻辑其实有蛮多不一样的地方 很多用这么多锁的地方感觉都在 golang 里面用起来怪怪的 这也是雪心新语言的一个必经之路吧 如果全用老的思维来处理问题 新语言的特性你也用不上了 那干嘛不直接用 Java 呢
iosyyy
2022-06-04 10:42:30 +08:00
@fds 直接用 copy on write 的思路不就行了吗..扯这么多不还是因为没有现成的实现吗..
keakon
2022-06-04 10:49:52 +08:00
你自己加个锁不就行了吗? sync.Map 单独实现出来是因为可以分片加锁,这样不同的 cpu 访问不同的分片时可以不用加锁,而其他数据结构没有这样的优化方法。
qwqaq
2022-06-04 11:05:11 +08:00
最近用到的一些 Golang 处理并发问题的方法:sync.Mutex 加互斥锁保证同一个时间点只有一个操作在进行;使用 sync.Once 初始化单个实例(例如懒汉式单例模式);使用 signleflight.Group 解决并发缓存查询问题(缓存击穿问题)
stevefan1999
2022-06-04 11:43:45 +08:00
stevefan1999
2022-06-04 11:47:11 +08:00
iyaozhen
2022-06-04 15:34:21 +08:00
其实吧 你自己说技术一般,那就不要多协程操作呀。或者是不要多协程操作一个 slice
ch2
2022-06-04 18:51:40 +08:00
chan 保平安,尽可能不要用锁,实在不行 sync.map 肯定够用了
george404
2022-06-04 19:17:56 +08:00
如果是用 slice 这些,如果是复杂的用法,你可以封成一个对象来访问啊。对象里面添加 lock 。这样你就不要每个调用的地方都放一个 RWlock 这些。
Binb
2022-06-04 20:24:22 +08:00
说的挺全了:锁、chan 、sync.Map, 还是不要并发好点,简单。
fds
2022-06-04 22:37:19 +08:00
@iosyyy 我说的 chan 或者锁,两者都跟 copy on write 没有关系。之所以 go 官方没有楼主说的功能,是因为不需要,用别的思路不需要 slice 本身线程安全。天下没有免费的午餐,保证线程安全是要有代价的,go 希望开发者自己选择承担多少锁的时间,而不是直接无脑使用一个库。
iosyyy
2022-06-04 23:48:37 +08:00
@fds 所以 go 语言本身使用协程希望并发都在用户态简单 然后不加一个很容易封装的 slice 说希望开发者选择吗? 站不住脚吧 同样的我举 copy on write 是指的 go 语言完全可以封装一个这种思路的对象然后 open 出来哪怕这东西很不自由 但是他很有用
iosyyy
2022-06-04 23:49:31 +08:00
@fds 如果想不无脑建议让 go 把线程放出来 不要使用协程这种无脑的方式
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
2022-06-05 12:34:36 +08:00
你自己加个 mutex 不就好了,数组这个数据结构太灵活了,如果你搞不懂 mutex 的话其实很容易出错。比如说你获取 index 然后删除应不应该是原子操作呢? shift 应不应该是原子操作呢?
alexmy
2022-06-05 22:48:00 +08:00
Actor 模式保命?看看这个。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/857197

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX