亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

在讀取和修改之前鎖定切片

在讀取和修改之前鎖定切片

Go
慕姐8265434 2023-02-21 12:40:26
我使用 Go 的經驗是最近的,在審查一些代碼時,我看到雖然它被寫保護,但讀取數據時存在問題。不是閱讀本身,而是閱讀和修改切片之間可能發生的修改。type ConcurrentSlice struct {    sync.RWMutex    items []Item}type Item struct {    Index int    Value Info}type Info struct {    Name        string     Labels      map[string]string    Failure     bool}如前所述,寫作以這種方式受到保護:func (cs *ConcurrentSlice) UpdateOrAppend(item ScalingInfo) {    found := false    i := 0    for inList := range cs.Iter() {        if item.Name == inList.Value.Name{            cs.items[i] = item            found = true        }        i++    }    if !found {        cs.Lock()        defer cs.Unlock()        cs.items = append(cs.items, item)    }}func (cs *ConcurrentSlice) Iter() <-chan ConcurrentSliceItem {    c := make(chan ConcurrentSliceItem)    f := func() {        cs.Lock()        defer cs.Unlock()        for index, value := range cs.items {            c <- ConcurrentSliceItem{index, value}        }        close(c)    }    go f()    return c}但是在收集切片的內容和修改它之間,可能會發生修改??赡苁橇硪粋€例程修改了同一個切片,當該賦值時,它已經不存在了:slice[i] = item處理這個問題的正確方法是什么?我已經實現了這個方法:func GetList() *ConcurrentSlice {    if list == nil {        denylist = NewConcurrentSlice()        return denylist    }    return denylist}我這樣使用它:concurrentSlice := GetList()concurrentSlice.UpdateOrAppend(item)但我知道在獲取和修改之間,即使它實際上是立即的,另一個例程也可能修改了切片。以原子方式執行這兩個操作的正確方法是什么?我閱讀的切片 100% 是我修改的切片。因為如果我試圖將一個項目分配給一個不再存在的索引,它會中斷執行。先感謝您!
查看完整描述

5 回答

?
幕布斯6054654

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)

    }

}


查看完整回答
反對 回復 2023-02-21
?
翻閱古今

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))

    })

}


查看完整回答
反對 回復 2023-02-21
?
慕哥6287543

TA貢獻1831條經驗 獲得超10個贊

數據結構 101:始終為您的用例選擇最佳數據結構。如果您要按名稱查找對象,那正是 map 的用途。如果您仍然需要維護項目的順序,則使用樹圖


并發 101:與事務一樣,您的互斥量應該是原子的、一致的和隔離的。你在這里隔離失敗,因為讀取的數據結構不在你的互斥鎖內。


您的代碼應如下所示:


func {

 mutex.lock

 defer mutex.unlock

 check map or treemap for name

 if exists update

 else add

}


查看完整回答
反對 回復 2023-02-21
?
拉莫斯之舞

TA貢獻1820條經驗 獲得超10個贊

經過一些測試,我可以說你擔心的情況確實會發生sync.RWMutex。我認為它也可能發生sync.Mutex,但我無法重現。也許我遺漏了一些信息,或者調用是有序的,因為它們都被阻止了,并且它們贖回鎖定權的順序是以某種方式排序的。

在沒有其他例程進入“沖突”的情況下保持兩個調用安全的一種方法是為該對象上的每個任務使用另一個互斥鎖。您將在讀寫之前鎖定該互斥鎖,并在完成后釋放它。您還必須在寫入(或讀?。┰搶ο蟮娜魏纹渌{用上使用該互斥鎖。您可以在 main.go 文件中找到我所說內容的實現。為了重現 RWMutex 的問題,您可以簡單地注釋 startTask 和 endTask 調用,并且該問題在終端輸出中可見。

編輯:我的第一個答案是錯誤的,因為我誤解了測試結果,并陷入了 OP 描述的情況。


查看完整回答
反對 回復 2023-02-21
?
慕娘9325324

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正在處理的索引的切片元素,因此競爭永遠不會表現出來。

比賽可以按如下方式進行:

  1. iter 中的 for 循環讀取切片的元素 0

  2. 元素通過通道發送。因此,切片接收發生在第一步之后。

  3. 接收端可能會更新切片的元素 0。到這里沒有問題。

  4. 然后發送 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 Iters 只有在沒有調用的情況下才能運行UpdateOrAppend。那是一個 RWMutex。

但是可以從帶鎖的地方Iter調用,所以不能真的調用,否則就是死鎖。UpdateOrAppendRLock

因此,您需要兩個版本Iter:一個可以在外部調用UpdateOrAppend,在 goroutine 中發出RLock,另一個只能從中調用UpdateOrAppend,不能調用RLock。


查看完整回答
反對 回復 2023-02-21
  • 5 回答
  • 0 關注
  • 172 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號