golang 記一次map中struct的管道造成死鎖的解決方式
阿新 • • 發佈:2021-08-25
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){ forinfo := 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()
}