1. 程式人生 > 其它 >golang 記一次map中struct的管道造成死鎖的解決方式

golang 記一次map中struct的管道造成死鎖的解決方式

golang用到管道的時候,如果沒有恰當的寫入和讀取等會遇到死鎖問題,今天我遇到了偏門的死鎖問題,解決過程如下

一 死鎖例項

type Person struct {
    name  string
    ch chan string
}

var wg sync.WaitGroup

func main(){
    p1 := Person{name: "wbw", }
    p2 := Person{name: "asa",}
    themap := make(map[string]Person)
    themap["p1"] = p1
    themap["p2"] = p2

    for _,ps := range themap{
        ps.ch = make(chan string
) wg.Add(1) go getinfo(ps) } for i := 0; i < 10; i++ { time.Sleep(1 * time.Second) themap["p1"].ch <- "a" themap["p2"].ch <- "b" } for _,ps := range themap{ close(ps.ch) } wg.Wait() } func getinfo(ps Person){ for
info := range ps.ch{ fmt.Println(ps.name,info) } wg.Done() }

二 問題分析

  2.1map中struct不能修改

結構體作為map的元素時,不能夠直接賦值給結構體的某個欄位,也就是map中的struct中的欄位不能夠直接定址。

  這個現象的解釋

關於golang中map的這種古怪的特性有這樣幾個觀點:

1)map作為一個封裝好的資料結構,由於它底層可能會由於資料擴張而進行遷移,所以拒絕直接定址,避免產生野指標;

2)map中的key在不存在的時候,賦值語句其實會進行新的k-v值的插入,所以拒絕直接定址結構體內的欄位,以防結構體不存在的時候可能造成的錯誤;

3)這可能和map的併發不安全性相關

  • x = y 這種賦值的方式,你必須知道 x的地址,然後才能把值 y 賦給 x。
  • 但 go 中的 map 的 value 本身是不可定址的,因為 map 的擴容的時候,可能要做 key/val pair遷移
  • value 本身地址是會改變的
  • 不支援定址的話又怎麼能賦值呢

  那麼我們不管解釋通不通,現象確實存在,本人也寫過指令碼驗證過

  2.1map中struct不能修改解決方式

  •  先用temp獲取map中的struct,修改temp,再講map中該struct替換成temp
     ---> 這種無法解決我們死鎖問題,因為temp中的管道寫訊息,我們再range該map的時候,新的temp讀訊息,顯然已經不是同一個管道,遂放棄
  •  指標,把map的value部分定義為對應型別的指標型別或是slice或是map時,這樣是可以更新v的內部欄位的
     ---> 此方案可解決,我們只需要讓map中person結構體和p1 p2指向同一個指標即可,接下來提供解決例項

三 debug例項

type Person struct {
    name  string
    ch chan string
}

var wg sync.WaitGroup

func main(){
    p1 := Person{name: "wbw", }
    p2 := Person{name: "asa",}
    themap := make(map[string]*Person)
    themap["p1"] = &p1
    themap["p2"] = &p2

    for _,ps := range themap{
        ps.ch = make(chan string)

        wg.Add(1)
        go getinfo(ps)
    }

    for i := 0; i < 10; i++ {
        time.Sleep(1 * time.Second)
        themap["p1"].ch <- "a"
        themap["p2"].ch <- "b"
    }

    for _,ps := range themap{
        close(ps.ch)
    }
    wg.Wait()

}

func getinfo(ps *Person){
    for info := range ps.ch{
        fmt.Println(ps.name,info)
    }
    wg.Done()
}