Golang Go语言中有没有线程安全的数据类型?
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 等常见数据结构,这样风格能统一,并且还能线程安全,谢谢各位。
我实在技术不太行,我写的话经常有并发问题,所以才来求这种线程安全的基础数据结构,我知如果用了这种线程安全的基础数据结构会导致性能下降,没关系,我的系统对性能要求没那么高。我要是有能力一个人实现上面这些,我就早全一个风格实现不来麻烦大家了。希望有大神能帮帮我,万分感谢。
Golang Go语言中有没有线程安全的数据类型?
更多关于Golang Go语言中有没有线程安全的数据类型?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
线程安全的数据类型,只能保证单条操作是原子的,不能保证逻辑流程也是线程安全的,不仅仅是性能的问题。
sync 包很简陋,因为这是不被推荐的方式,只能适用于某些情况,并不能一把梭。
多线程编程本来就是很考验逻辑的,建议用 JS 。
用 chan 的意思是,把对同一 slice 的操作控制在一个 goroutine 里,这样就不用考虑锁的问题。
没有现成的是因为锁的需求场景都不一样。比如有两个 slice ,你可以用同一个锁限制他们的访问。拿到锁了,就可以对 slice 做很多放多操作。但是如果是别人包装好的,为了保证不出错,可能每次查询都要锁一次,效率太低。
好久没写 Go 了,隐约记得之前解决这种问题都直接加一个 sync.Mutex ,非常粗暴但有效而且易懂
Java 和 golang 的编程逻辑其实有蛮多不一样的地方 很多用这么多锁的地方感觉都在 golang 里面用起来怪怪的 这也是雪心新语言的一个必经之路吧 如果全用老的思维来处理问题 新语言的特性你也用不上了 那干嘛不直接用 Java 呢
直接用 copy on write 的思路不就行了吗…扯这么多不还是因为没有现成的实现吗…
最近用到的一些 Golang 处理并发问题的方法:sync.Mutex 加互斥锁保证同一个时间点只有一个操作在进行;使用 sync.Once 初始化单个实例(例如懒汉式单例模式);使用 signleflight.Group 解决并发缓存查询问题(缓存击穿问题)
其实吧 你自己说技术一般,那就不要多协程操作呀。或者是不要多协程操作一个 slice
如果是用 slice 这些,如果是复杂的用法,你可以封成一个对象来访问啊。对象里面添加 lock 。这样你就不要每个调用的地方都放一个 RWlock 这些。
说的挺全了:锁、chan 、sync.Map, 还是不要并发好点,简单。
我说的 chan 或者锁,两者都跟 copy on write 没有关系。之所以 go 官方没有楼主说的功能,是因为不需要,用别的思路不需要 slice 本身线程安全。天下没有免费的午餐,保证线程安全是要有代价的,go 希望开发者自己选择承担多少锁的时间,而不是直接无脑使用一个库。
所以 go 语言本身使用协程希望并发都在用户态简单 然后不加一个很容易封装的 slice 说希望开发者选择吗? 站不住脚吧 同样的我举 copy on write 是指的 go 语言完全可以封装一个这种思路的对象然后 open 出来哪怕这东西很不自由 但是他很有用
如果想不无脑建议让 go 把线程放出来 不要使用协程这种无脑的方式
哦,你是说 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 文档里说了,只在特定场景中使用。
你后面说的线程我不太理解,协程已经把线程池这个概念封装掉了,不用让每个程序员都直接处理一些底层优化。不清楚你说的无脑是什么意思。
你自己加个 mutex 不就好了,数组这个数据结构太灵活了,如果你搞不懂 mutex 的话其实很容易出错。比如说你获取 index 然后删除应不应该是原子操作呢? shift 应不应该是原子操作呢?
Actor 模式保命?看看这个。
不要通过共享内存来通信
在Golang(Go语言)中,确实存在线程安全的数据类型,主要体现在复合数据类型和一些特定操作上。以下是一些关键的线程安全数据类型和机制:
- Channel:Channel是Golang中实现线程间安全通讯的核心机制。它本质上是一个环形队列,有读、写索引和互斥锁,确保数据在多个goroutine之间传递时的安全性。
- Map:在Go 1.9及以后的版本中,对map的并发读写需要使用sync.Map或者通过其他同步机制(如Mutex)来保护对map的访问。sync.Map提供了一些并发安全的操作方法,如Store、Load、Delete和Range等。
- Slice和Array:虽然Slice和Array本身是值类型,不是线程安全的,但可以通过使用sync包中的Mutex或RWMutex等同步原语来实现对Slice或Array的并发安全访问。
需要注意的是,虽然Golang提供了一些线程安全的数据类型和机制,但在实际编程中,开发者仍需谨慎处理并发问题,避免数据竞争和死锁等并发错误。正确的并发编程实践包括合理设计数据结构、使用适当的同步原语以及进行充分的测试等。