os.Interrupt失效:如何通过信号终止Go程序?
解决Go程序无法响应Ctrl+C信号退出的问题
问题根源
你的代码中,子goroutine的循环使用了select+default分支,这会导致循环持续抢占式执行network.StartSending()和network.StartListening(),几乎没有机会检查ctx.Done()的取消信号。因为select在无就绪case时会立刻进入default分支,整个循环被这两个函数占满,完全无法响应上下文取消,最终主goroutine触发cancel()后,子goroutine也无法退出,程序自然无法终止。
另外,network.SafetyClose()被同时放在defer和ctx.Done()分支中,会导致重复调用,可能引发不必要的错误。
修复方案
- 移除原
select的default分支,让goroutine能优先响应ctx.Done()信号; - 将
StartSending/StartListening拆分到独立goroutine中运行,避免阻塞主循环的取消监听; - 调整
SafetyClose()的调用逻辑,避免重复关闭资源; - 如果
StartSending/StartListening是无阻塞的高频循环,建议添加短暂休眠,避免占满CPU。
修改后的代码
package main import ( "context" "os" "os/signal" "sync" "syscall" ses "github.com/jame/SES-algorithm/SES" "github.com/jame/SES-algorithm/network" ) func main() { numberProcess := 3 c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) ctx, cancel := context.WithCancel(context.Background()) defer cancel() // 确保主函数退出时自动取消上下文 var wg sync.WaitGroup for i := 0; i < numberProcess; i++ { wg.Add(1) go func(instanceID int) { defer wg.Done() ses.InitLog(instanceID) network := network.NewNetwork(instanceID, numberProcess) defer network.SafetyClose() // 仅在goroutine退出时调用一次关闭 // 启动发送、监听的独立goroutine var netWg sync.WaitGroup netWg.Add(2) // 发送任务循环 go func() { defer netWg.Done() for { select { case <-ctx.Done(): return default: network.StartSending() // 若StartSending无阻塞,添加休眠避免CPU满载 // time.Sleep(time.Millisecond * 10) } } }() // 监听任务循环 go func() { defer netWg.Done() for { select { case <-ctx.Done(): return default: network.StartListening() // 同上,根据实际阻塞情况调整休眠 // time.Sleep(time.Millisecond * 10) } } }() // 等待网络任务goroutine全部退出 netWg.Wait() }(i) } // 等待OS中断信号 <-c // 触发所有goroutine的取消信号 cancel() // 等待所有子goroutine完成清理 wg.Wait() os.Exit(0) }
额外建议
如果network包支持上下文参数,建议修改StartSending和StartListening函数,让它们直接接收ctx,这样可以在函数内部监听取消信号,无需额外的循环判断,代码会更简洁可靠。
内容的提问来源于stack exchange,提问作者Legend Never Die
相关产品推荐
相关产品推荐

