5 回答
TA贡献1876条经验 获得超7个赞
您进行阻止的方式是不正确的,因为它不能确保您退回的物品没有被移除。在更新的情况下,数组仍将至少保持相同的长度。
一个更简单的可行解决方案如下:
func (cs *ConcurrentSlice) UpdateOrAppend(item ScalingInfo) {
found := false
i := 0
cs.Lock()
defer cs.Unlock()
for _, it := range cs.items {
if item.Name == it.Name{
cs.items[i] = it
found = true
}
i++
}
if !found {
cs.items = append(cs.items, item)
}
}
TA贡献1780条经验 获得超5个赞
如果值的顺序不重要,请使用sync.Map 。
type Items struct {
m sync.Map
}
func (items *Items) Update(item Info) {
items.m.Store(item.Name, item)
}
func (items *Items) Range(f func(Info) bool) {
items.m.Range(func(key, value any) bool {
return f(value.(Info))
})
}
TA贡献1831条经验 获得超10个赞
数据结构 101:始终为您的用例选择最佳数据结构。如果您要按名称查找对象,那正是 map 的用途。如果您仍然需要维护项目的顺序,则使用树图
并发 101:与事务一样,您的互斥量应该是原子的、一致的和隔离的。你在这里隔离失败,因为读取的数据结构不在你的互斥锁内。
您的代码应如下所示:
func {
mutex.lock
defer mutex.unlock
check map or treemap for name
if exists update
else add
}
TA贡献1820条经验 获得超10个赞
经过一些测试,我可以说你担心的情况确实会发生sync.RWMutex
。我认为它也可能发生sync.Mutex
,但我无法重现。也许我遗漏了一些信息,或者调用是有序的,因为它们都被阻止了,并且它们赎回锁定权的顺序是以某种方式排序的。
在没有其他例程进入“冲突”的情况下保持两个调用安全的一种方法是为该对象上的每个任务使用另一个互斥锁。您将在读写之前锁定该互斥锁,并在完成后释放它。您还必须在写入(或读取)该对象的任何其他调用上使用该互斥锁。您可以在 main.go 文件中找到我所说内容的实现。为了重现 RWMutex 的问题,您可以简单地注释 startTask 和 endTask 调用,并且该问题在终端输出中可见。
编辑:我的第一个答案是错误的,因为我误解了测试结果,并陷入了 OP 描述的情况。
TA贡献1783条经验 获得超4个赞
如果ConcurrentSlice要从单个 goroutine 使用,则不需要锁,因为在那里编写的算法不会对 slice 元素或 slice 进行任何并发读/写。
如果ConcurrentSlice要从多个 goroutines 使用,现有的锁是不够的。这是因为UpdateOrAppend可能同时修改切片元素。
安全版本需要两个版本Iter:
这可以由 的用户调用ConcurrentSlice,但不能从 `UpdateOrAppend 中调用:
func (cs *ConcurrentSlice) Iter() <-chan ConcurrentSliceItem {
c := make(chan ConcurrentSliceItem)
f := func() {
cs.RLock()
defer cs.RUnlock()
for index, value := range cs.items {
c <- ConcurrentSliceItem{index, value}
}
close(c)
}
go f()
return c
}
这只能从以下位置调用UpdateOrAppend:
func (cs *ConcurrentSlice) internalIter() <-chan ConcurrentSliceItem {
c := make(chan ConcurrentSliceItem)
f := func() {
// No locking
for index, value := range cs.items {
c <- ConcurrentSliceItem{index, value}
}
close(c)
}
go f()
return c
}
并且UpdateOrAppend应该在顶层同步:
func (cs *ConcurrentSlice) UpdateOrAppend(item ScalingInfo) {
cs.Lock()
defer cs.Unlock()
....
}
这是长版:
这是一段有趣的代码。Iter()根据我对 go 内存模型的理解,只有当有另一个 goroutine 在处理这段代码时才需要互斥锁,即使这样,代码中也可能存在竞争。但是,UpdateOrAppend只修改索引低于Iter正在处理的索引的切片元素,因此竞争永远不会表现出来。
比赛可以按如下方式进行:
iter 中的 for 循环读取切片的元素 0
元素通过通道发送。因此,切片接收发生在第一步之后。
接收端可能会更新切片的元素 0。到这里没有问题。
然后发送 goroutine 读取切片的元素 1。这是比赛可以发生的时候。如果第 3 步更新了切片的索引 1,则第 4 步的读取是一场竞赛。即:如果第 3 步读取到第 4 步完成的更新,则这是一场比赛。如果您在 UpdateOrAppend 中以 i:=1 开始,并使用 -race 标志运行它,您可以看到这一点。
但是UpdateOrAppend
总是修改 i=0 时已经看到的切片元素Iter
,所以这段代码是安全的,即使没有锁。
如果有其他 goroutines 访问和修改结构,你需要 Mutex,但你需要它来保护完整的UpdateOrAppend
方法,因为应该只允许一个 goroutine 运行它。您需要互斥锁来保护第一个 for 循环中的潜在更新,并且该互斥锁还必须包括切片追加情况,因为这实际上可能会修改底层对象的切片。
如果Iter
仅调用 from UpdateOrAppend
,那么这个单个互斥锁就足够了。但是,如果Iter
可以从多个 goroutines 调用,那么还有另一种竞争可能性。如果一个实例UpdateOrAppend
与多个实例并发运行Iter
,那么其中一些Iter
实例将同时从修改后的切片元素中读取,从而导致竞争。所以,应该是 multiple Iter
s 只有在没有调用的情况下才能运行UpdateOrAppend
。那是一个 RWMutex。
但是可以从带锁的地方Iter
调用,所以不能真的调用,否则就是死锁。UpdateOrAppend
RLock
因此,您需要两个版本Iter
:一个可以在外部调用UpdateOrAppend
,在 goroutine 中发出RLock
,另一个只能从中调用UpdateOrAppend
,不能调用RLock
。
- 5 回答
- 0 关注
- 129 浏览
添加回答
举报