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

Go语言中调用context cancel()后等待goroutine完成的最佳实践

最佳实践:用退出通道同步goroutine完成

核心思路是新增一个done通道,专门用来等待run goroutine执行完毕,替代不可靠的time.Sleep或重量级的WaitGroup(毕竟只有单个goroutine)。

修改后的代码实现

main包代码

func main() {
    log.Infoln("starting service..")
    ctx := context.Background()
    srv := service.Service{}

    exitCh := make(chan os.Signal, 1)
    signal.Notify(exitCh, syscall.SIGTERM, // terminate: 对应`kill PID`(注意:kill -9是强制杀死,无法被程序捕获)
        syscall.SIGINT, // interrupt: 对应Ctrl + C
    )

    ctxCancel, cancel := context.WithCancel(ctx)
    done := make(chan struct{}) // 新增:用于等待goroutine退出的同步通道
    go run(ctxCancel, srv, done)

    <-exitCh // 阻塞直到收到系统退出信号
    cancel() // 触发context取消,让service.Start()进入退出逻辑
    <-done   // 等待goroutine完全执行完毕后,再退出main函数
}

func run(ctx context.Context, srv service.Service, done chan<- struct{}) {
    defer close(done) // 函数退出时关闭done通道,通知main已完成所有逻辑
    err := srv.Start(ctx)
    if err != nil {
        log.Warningf("canceling all operations: %s", err)
    }
}

service包代码(无需修改)

type Service struct {}

func (s *Service) Start(ctx context.Context) error {
    for i := 0; i < 5; i++ {
        select {
        case <-ctx.Done():
            log.Println("Done")
            return ctx.Err()
        default:
            log.Printf("i value: %d", i)
            time.Sleep(3 * time.Second)
        }
    }
    return nil
}

方案说明

  • 新增的done通道是无缓冲的struct{}类型,仅用于同步单个goroutine的退出状态,比WaitGroup更简洁,不需要初始化、计数、等待那套流程。
  • run函数中用defer close(done),确保不管Start函数是正常返回还是因context取消返回,都会触发done通道的关闭,main里的<-done会被唤醒,保证main不会提前退出。
  • 移除了原run函数中向exitCh发送信号的逻辑——这部分是多余的,因为main已经在监听系统信号,run的退出不需要再向该通道发信号,避免潜在的通道死锁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 20:08:26