发生panic时向Go channel发送数据为何会阻塞?
Go无缓冲通道发送阻塞问题分析
你遇到的情况是:在processData传入空指针触发panic后,defer中向l.done通道发送数据的操作发生阻塞,但通道并未关闭。
问题代码
package main import ( "context" "fmt" "time" ) type Loop struct { done chan struct{} } type Result struct { ID string } func NewLoop() *Loop { return &Loop{ done: make(chan struct{}), } } func (l *Loop) StartAsync(ctx context.Context) { go func() { defer func() { l.done <- struct{}{} // BLOCKED! But I allocated it in NewLoop ctor fmt.Sprintf("done") }() for { var data *Result l.processData(ctx, data) // passed nil } }() } func (l *Loop) processData(ctx context.Context, data *Result) { _ = fmt.Sprintf("%s", data.ID) // intentional panic - OK } func main() { l := NewLoop() l.StartAsync(context.Background()) time.Sleep(10 * time.Second) }
原因解析
你的done通道是无缓冲通道(make(chan struct{})未指定缓冲大小,默认创建无缓冲通道)。无缓冲通道的核心特性是:
- 发送操作必须等待对应的接收操作完成才能结束
- 接收操作必须等待对应的发送操作完成才能结束
在你的代码逻辑里:
StartAsync启动的goroutine触发panic后,进入defer函数执行l.done <- struct{}{}- 但整个程序中没有任何goroutine对
done通道执行接收操作,main函数只是单纯sleep,完全没处理这个通道 - 因此发送操作找不到对应的接收方,会一直阻塞,直到程序退出
解决方法
有两种可行的修正方式:
- 将
done改为带缓冲的通道:在NewLoop中创建通道时指定缓冲大小,比如done: make(chan struct{}, 1),这样发送操作可以直接把数据存入缓冲区,无需等待接收方 - 在main函数中添加接收逻辑:把
time.Sleep(10 * time.Second)替换为<-l.done,让main goroutine等待异步goroutine的退出信号,这样发送操作就能正常完成
内容的提问来源于stack exchange,提问作者DisplayName
相关产品推荐
相关产品推荐

