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

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()分支中,会导致重复调用,可能引发不必要的错误。

修复方案

  1. 移除原select的default分支,让goroutine能优先响应ctx.Done()信号;
  2. 将StartSending/StartListening拆分到独立goroutine中运行,避免阻塞主循环的取消监听;
  3. 调整SafetyClose()的调用逻辑,避免重复关闭资源;
  4. 如果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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 07:22:50