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 關注
- 172 瀏覽
添加回答
舉報