Golang代码重构后出现close of closed channel panic求助
问题根因
你的panic完全是defer的使用时机和逻辑错误导致的,核心有两点:
- defer触发时机错误:Go中defer语句会在当前函数退出前执行,你把defer写在
GetDecisListener内部的循环里,意味着GetDecisListener刚执行完return把listener返回给上层LiveDecision的瞬间,所有注册的defer就会依次执行,直接把还没开始用的listener关掉、解注册。 - 重复关闭同一channel:你循环了多少次就注册了多少个defer函数,所有defer都指向同一个listener变量,第一个defer执行完
close(listener)后,后续的defer再执行关闭操作就会触发close of closed channel的panic,和你报错栈的信息完全匹配。
解决方案
核心逻辑是将listener的生命周期和上层HTTP请求的生命周期对齐,不要在GetDecisListener内部做清理操作,等到请求处理完毕后再统一清理:
1. 修改GetDecisListener逻辑
移除内部的defer,新增返回值记录所有注册过的key:
//GetDecisListener return listener subscribed to channels and all registered keys func GetDecisListener(dStructs map[string]map[string]interface{}) (chan interface{}, []string, error) { listener := make(chan interface{}) keys := make([]string, 0, len(dStructs)) for r := range dStructs { DecisChannels[r].Register(listener) keys = append(keys, r) fmt.Printf("Done %v\n", r) } fmt.Print("Done inside\n") return listener, keys, nil }
2. 修改LiveDecision逻辑
在请求层添加defer,等整个请求处理完成后再统一执行解注册、关闭channel操作:
func LiveDecision(ctx *gin.Context) { ... listener, keys, err := GetDecisListener(dStructs) fmt.Print("Done outside\n") if err != nil {return} // 请求结束后统一清理listener defer func() { for _, key := range keys { Decision(key).Unregister(listener) } close(listener) }() ctx.Stream(func(w io.Writer) bool { select { case msg := <-listener: // 原有消息处理逻辑 return true ... case <-ctx.Request.Context().Done(): // 客户端断开连接时退出stream return false } }) }
调整说明
- 清理逻辑移到
LiveDecision层后,defer会在整个HTTP请求处理完毕(包括Stream流推送结束、客户端断开连接)后才触发,保证listener在使用周期内始终可用 - 所有key仅执行一次解注册,整个listener仅关闭一次,完全避免重复关闭的panic问题
内容的提问来源于stack exchange,提问作者Simon
相关产品推荐
相关产品推荐

