You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

发生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{})未指定缓冲大小,默认创建无缓冲通道)。无缓冲通道的核心特性是:

  • 发送操作必须等待对应的接收操作完成才能结束
  • 接收操作必须等待对应的发送操作完成才能结束

在你的代码逻辑里:

  1. StartAsync启动的goroutine触发panic后,进入defer函数执行l.done <- struct{}{}
  2. 但整个程序中没有任何goroutine对done通道执行接收操作,main函数只是单纯sleep,完全没处理这个通道
  3. 因此发送操作找不到对应的接收方,会一直阻塞,直到程序退出

解决方法

有两种可行的修正方式:

  • 将done改为带缓冲的通道:在NewLoop中创建通道时指定缓冲大小,比如done: make(chan struct{}, 1),这样发送操作可以直接把数据存入缓冲区,无需等待接收方
  • 在main函数中添加接收逻辑:把time.Sleep(10 * time.Second)替换为<-l.done,让main goroutine等待异步goroutine的退出信号,这样发送操作就能正常完成

内容的提问来源于stack exchange,提问作者DisplayName

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 01:18:18