Go语言如何在goroutine执行完毕后读取channel值并解决死锁
问题解答
如何在pushNodes的goroutine结束后仍访问channel的值
只要你使用带缓冲的channel,写入到channel缓冲内的数据不会因为写goroutine退出而消失,后续执行的读goroutine仍然可以正常读取到缓冲内的内容。如果使用无缓冲channel,必须有读goroutine同时等待才能写入成功,不符合你先全部写完再读的逻辑。
死锁原因分析
- 直接原因:你当前使用的是无缓冲channel,且
pushNodes逻辑里只有当前节点infected=true时才会往随机目标节点的channel写数据,如果某个节点的channel从未被写入过数据,updateNodes执行到receivedchanneldata := <-node.channel时会永久阻塞等待读操作,最终所有goroutine都陷入等待触发死锁。 - 隐藏问题:你
pushNodes和updateNodes的入参都是值传递的Node,修改参数里的node.infected、node.message根本不会生效到全局的allnodes数组里,业务逻辑也不会符合预期。
修复方案
按照你要求的「所有push执行完毕后再执行update」的逻辑,修改如下:
- 把所有node的channel改成缓冲长度为1的缓冲channel,保证写操作不会阻塞
- 所有push执行完成后关闭全部channel,避免读操作永久阻塞
- 函数入参改成
*Node指针类型,保证修改可以同步到全局变量
修改后的代码示例:
// Node结构体初始化时,给channel加1个缓冲 type Node struct { // 其他原有字段 channel chan ChannelData number int infected bool message string } // 初始化Node时执行 node.channel = make(chan ChannelData, 1) // pushNodes 修改入参为指针 func pushNodes(node *Node) { defer wg.Done() fmt.Printf("Pushing: %d \n", node.number) var randomnode int = rand.Intn(totalnodes) for randomnode == node.number { randomnode = rand.Intn(totalnodes) } if node.infected { sentchanneldata := ChannelData{infected: true, message: node.message} allnodes[randomnode].channel <- sentchanneldata fmt.Printf("Node %d sent a value of %t and %s to node %d!\n", node.number, sentchanneldata.infected, sentchanneldata.message, allnodes[randomnode].number) } } // updateNodes 修改入参为指针 func updateNodes(node *Node) { defer wg.Done() fmt.Printf("Updating: %d\n", node.number) // 读channel判断是否有有效数据 receivedchanneldata, ok := <-node.channel if ok { fmt.Printf("Node %d received a value of %t and %s!\n", node.number, receivedchanneldata.infected, receivedchanneldata.message) if receivedchanneldata.infected { node.infected = true } if receivedchanneldata.message != "" { node.message = receivedchanneldata.message } fmt.Printf("Update successful!\n") } else { fmt.Printf("Node %d has no new data to update\n", node.number) } } // main逻辑部分 rand.Seed(time.Now().UnixNano()) // 全局只调用一次随机种子即可 wg.Add(totalnodes) for i := range allnodes { go pushNodes(&allnodes[i]) // 传指针 } wg.Wait() fmt.Println("Infect function done!") // 所有推送完成后关闭全部node的channel for i := range allnodes { close(allnodes[i].channel) } wg.Add(totalnodes) for i := range allnodes { go updateNodes(&allnodes[i]) // 传指针 } wg.Wait()
优化建议
如果你的节点数量很大,用channel传递单轮更新数据的开销较高,也可以直接用全局切片存每个节点的待更新数据,push的时候写切片,update的时候读切片,完全避开channel的阻塞问题,实现更简单。
内容的提问来源于stack exchange,提问作者mringes
相关产品推荐
相关产品推荐

