一 死锁实例
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.1 map中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.2 map中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() }