5 回答

TA貢獻(xiàn)1876條經(jīng)驗 獲得超7個贊
您進(jìn)行阻止的方式是不正確的,因為它不能確保您退回的物品沒有被移除。在更新的情況下,數(shù)組仍將至少保持相同的長度。
一個更簡單的可行解決方案如下:
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貢獻(xiàn)1780條經(jīng)驗 獲得超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貢獻(xiàn)1831條經(jīng)驗 獲得超10個贊
數(shù)據(jù)結(jié)構(gòu) 101:始終為您的用例選擇最佳數(shù)據(jù)結(jié)構(gòu)。如果您要按名稱查找對象,那正是 map 的用途。如果您仍然需要維護項目的順序,則使用樹圖
并發(fā) 101:與事務(wù)一樣,您的互斥量應(yīng)該是原子的、一致的和隔離的。你在這里隔離失敗,因為讀取的數(shù)據(jù)結(jié)構(gòu)不在你的互斥鎖內(nèi)。
您的代碼應(yīng)如下所示:
func {
mutex.lock
defer mutex.unlock
check map or treemap for name
if exists update
else add
}

TA貢獻(xiàn)1820條經(jīng)驗 獲得超10個贊
經(jīng)過一些測試,我可以說你擔(dān)心的情況確實會發(fā)生sync.RWMutex
。我認(rèn)為它也可能發(fā)生sync.Mutex
,但我無法重現(xiàn)。也許我遺漏了一些信息,或者調(diào)用是有序的,因為它們都被阻止了,并且它們贖回鎖定權(quán)的順序是以某種方式排序的。
在沒有其他例程進(jìn)入“沖突”的情況下保持兩個調(diào)用安全的一種方法是為該對象上的每個任務(wù)使用另一個互斥鎖。您將在讀寫之前鎖定該互斥鎖,并在完成后釋放它。您還必須在寫入(或讀?。┰搶ο蟮娜魏纹渌{(diào)用上使用該互斥鎖。您可以在 main.go 文件中找到我所說內(nèi)容的實現(xiàn)。為了重現(xiàn) RWMutex 的問題,您可以簡單地注釋 startTask 和 endTask 調(diào)用,并且該問題在終端輸出中可見。
編輯:我的第一個答案是錯誤的,因為我誤解了測試結(jié)果,并陷入了 OP 描述的情況。

TA貢獻(xiàn)1783條經(jīng)驗 獲得超4個贊
如果ConcurrentSlice要從單個 goroutine 使用,則不需要鎖,因為在那里編寫的算法不會對 slice 元素或 slice 進(jìn)行任何并發(fā)讀/寫。
如果ConcurrentSlice要從多個 goroutines 使用,現(xiàn)有的鎖是不夠的。這是因為UpdateOrAppend可能同時修改切片元素。
安全版本需要兩個版本Iter:
這可以由 的用戶調(diào)用ConcurrentSlice,但不能從 `UpdateOrAppend 中調(diào)用:
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
}
這只能從以下位置調(diào)用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應(yīng)該在頂層同步:
func (cs *ConcurrentSlice) UpdateOrAppend(item ScalingInfo) {
cs.Lock()
defer cs.Unlock()
....
}
這是長版:
這是一段有趣的代碼。Iter()根據(jù)我對 go 內(nèi)存模型的理解,只有當(dāng)有另一個 goroutine 在處理這段代碼時才需要互斥鎖,即使這樣,代碼中也可能存在競爭。但是,UpdateOrAppend只修改索引低于Iter正在處理的索引的切片元素,因此競爭永遠(yuǎn)不會表現(xiàn)出來。
比賽可以按如下方式進(jìn)行:
iter 中的 for 循環(huán)讀取切片的元素 0
元素通過通道發(fā)送。因此,切片接收發(fā)生在第一步之后。
接收端可能會更新切片的元素 0。到這里沒有問題。
然后發(fā)送 goroutine 讀取切片的元素 1。這是比賽可以發(fā)生的時候。如果第 3 步更新了切片的索引 1,則第 4 步的讀取是一場競賽。即:如果第 3 步讀取到第 4 步完成的更新,則這是一場比賽。如果您在 UpdateOrAppend 中以 i:=1 開始,并使用 -race 標(biāo)志運行它,您可以看到這一點。
但是UpdateOrAppend
總是修改 i=0 時已經(jīng)看到的切片元素Iter
,所以這段代碼是安全的,即使沒有鎖。
如果有其他 goroutines 訪問和修改結(jié)構(gòu),你需要 Mutex,但你需要它來保護完整的UpdateOrAppend
方法,因為應(yīng)該只允許一個 goroutine 運行它。您需要互斥鎖來保護第一個 for 循環(huán)中的潛在更新,并且該互斥鎖還必須包括切片追加情況,因為這實際上可能會修改底層對象的切片。
如果Iter
僅調(diào)用 from UpdateOrAppend
,那么這個單個互斥鎖就足夠了。但是,如果Iter
可以從多個 goroutines 調(diào)用,那么還有另一種競爭可能性。如果一個實例UpdateOrAppend
與多個實例并發(fā)運行Iter
,那么其中一些Iter
實例將同時從修改后的切片元素中讀取,從而導(dǎo)致競爭。所以,應(yīng)該是 multiple Iter
s 只有在沒有調(diào)用的情況下才能運行UpdateOrAppend
。那是一個 RWMutex。
但是可以從帶鎖的地方Iter
調(diào)用,所以不能真的調(diào)用,否則就是死鎖。UpdateOrAppend
RLock
因此,您需要兩個版本Iter
:一個可以在外部調(diào)用UpdateOrAppend
,在 goroutine 中發(fā)出RLock
,另一個只能從中調(diào)用UpdateOrAppend
,不能調(diào)用RLock
。
- 5 回答
- 0 關(guān)注
- 188 瀏覽
添加回答
舉報