Golang Go语言中怎么实现一个并发安全的,对写多读少友好的数据结构?

发布于 1周前 作者 nodeper 来自 Go语言

实现过一个并发安全的 slice ,但只支持 append 操作。见: https://github.com/wencan/gox

现在需要一个支持删除的

sync.Map 支持 store 和 delete ,但写多了就会经常加锁

其中一个使用场景如下:
每接受一个请求,把该请求添加到集合。
请求处理完,从集合删除请求。
服务退出时,检查集合里还有哪些请求。

求思路
Golang Go语言中怎么实现一个并发安全的,对写多读少友好的数据结构?


更多关于Golang Go语言中怎么实现一个并发安全的,对写多读少友好的数据结构?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

14 回复

并发安全, 要么加锁,要么 channel 吧, sync.map 适合读多写少的场景, 写多读少, 用 rwlook 呢, 再加个 分片, 对 key 取值, 分散到不同的 key 中, 减小锁的粒度

更多关于Golang Go语言中怎么实现一个并发安全的,对写多读少友好的数据结构?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你确定你写的 slice 是并发安全的么?我看这里的讨论
https://groups.google.com/g/golang-nuts/c/cQdZJMQSxTE/m/K_IrQfoZBQAJ
- any global variables. Even reading an ‘int’ while some other goroutine is updating it is unsafe; it may give undefined behavior.
在你用新 arrays 覆盖旧 arrays 的瞬间,有没有可能别的 goroutine 读到一个错误的地址呢?

你说的应用场景我觉得 buffered channel 就可以吧,比如设置大小为 1000 ,有 10 个 worker 从里面读请求。如果要退出,可以另外启动一个 goroutine 从里面把剩下的 log 下来。

。。。就连一个 int 也不是并发安全的,OP 说他自己封装了一个


根据你的描述,我找到了 Slice 的 Append 方法 https://github.com/wencan/gox/blob/main/xsync/slice.go#L183
Append 下面的 Grow 段是加锁的
覆盖是通过 sync.Value 操作的
因此不存在你说的情况
不知道我有没有理解错你的意思

你那个 grow 中的

array := new([8]atomic.Value)
tail = array[:]

有啥意义?。。。

新增的空间
你再看 Grow 函数下面那段

确实没啥用啊,和直接 make 一个有啥区别?

以朋友的想法,应该怎么实现一个并发安全的 slice ?最好还支持 delete

哦,对,是我没看到下面 Slice 的具体实现,只看了最上面的 comment 就下结论了,太草率。

不过我觉得这样套很多 atomic 只是单元素的读性能会好,如果批量读写大量数据就不好说了,应用场景比较特殊。一般用 go 写的应用,只要锁的粒度不太大,应该不会成为瓶颈吧?网络请求、磁盘 IO 的时间比这都大多了。

既然是写多读少,每个协程都使用自己的 slice , 读的时候再加锁

我是单说你这个代码。

就实现来说,数组需要 delete 本身就是不太合理的,你可能需要 link/heap/map? 并发安全而言,就直接加锁吧,锁和原子变量性能上其实没多大差别; free-lock 其实比较鸡肋


slice 只是个名字而已,没必要纠结“slice” delete 操作是否合理。
最终实现,应该不会叫 slice 。到时具体看底层数据结构,或者另外取个跟底层数据结构无关的名字。
根据基准测试结果,基于 mutex 和基于原子的 free-lock 操作,两者性能差距还是很明显。

在Go语言中,实现一个并发安全且对写多读少友好的数据结构,通常会使用到Go的并发原语,如互斥锁(sync.Mutex)或读写锁(sync.RWMutex)。由于你提到的是“写少读多”的场景,读写锁(sync.RWMutex)会是一个更为合适的选择,因为它允许多个读操作同时进行,而写操作则是独占的。

以下是一个简单的示例,展示了如何使用sync.RWMutex来实现一个线程安全的Map结构:

package main

import (
	"fmt"
	"sync"
)

type SafeMap struct {
	mu sync.RWMutex
	m  map[string]interface{}
}

func NewSafeMap() *SafeMap {
	return &SafeMap{m: make(map[string]interface{})}
}

func (sm *SafeMap) Set(key string, value interface{}) {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	sm.m[key] = value
}

func (sm *SafeMap) Get(key string) (interface{}, bool) {
	sm.mu.RLock()
	defer sm.mu.RUnlock()
	value, exists := sm.m[key]
	return value, exists
}

func main() {
	// 使用示例
}

在这个示例中,SafeMap结构体内部维护了一个sync.RWMutex和一个普通的map。Set方法用于写入数据,它获取写锁以保证写入时的原子性;Get方法用于读取数据,它获取读锁以允许并发读取。这种设计使得SafeMap在“写少读多”的场景下具有较高的并发性能。

回到顶部