Windows下使用Makefile运行Go程序时Ctrl+C优雅退出问题
我在Windows 11系统中使用以下Makefile运行Go应用:
build: go build -o ./bin/my-service ./cmd/my-service cls: build cmd /c cls # Run the application run: cls ./bin/my-service # Clean build artifacts clean: rmdir /s /q bin
执行make run命令后程序能正常启动,但我的Go应用包含如下优雅关闭逻辑:
// 创建WaitGroup等待goroutines完成 var wg sync.WaitGroup // 创建通道监听系统信号(如Ctrl+C) sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) // 创建可取消的context ctx, cancel := context.WithCancel(context.Background()) defer cancel() // 启动需要优雅停止的goroutine wg.Add(1) go startGoroutine(ctx, &wg) // 阻塞直到收到信号(如Ctrl+C) <-sigChan cancel() // 收到信号,处理优雅关闭 fmt.Println("Received shutdown signal, stopping goroutines...") // 执行必要的清理、停止goroutine等操作 // 等待goroutine完成 log.Println("waiting for wg.wait") wg.Wait() time.Sleep(10 * time.Second) fmt.Println("Shutdown complete")
问题:按下Ctrl+C后,程序会立即终止,10秒休眠和"Shutdown complete"日志会在后台执行并在光标后输出,无法维持程序直到执行完最后一行。但直接用go run main.go运行时,优雅关闭逻辑可正常工作。
我尝试过将Makefile中的run命令替换为cmd /c start /wait "C:\full_path_to_app\my-service.exe",这个方法能正常触发优雅退出,但需要在build命令中指定.exe后缀,且必须使用绝对路径,同时会在新窗口中运行程序,而非当前终端窗口。
请问是否有方法能让make等待程序在按下Ctrl+C后完全退出?
问题核心是Windows下的make(如GNU Make)收到Ctrl+C时会直接终止自身及所有子进程,不会等待Go程序完成优雅关闭流程。以下是几种可行的解决办法:
方法1:适配Shell终端的信号转发
如果使用Git Bash、WSL这类类Unix终端,修改Makefile的run目标,让make忽略SIGINT信号,把信号传递给Go程序:
run: cls @trap "" INT; ./bin/my-service
trap "" INT会让make不处理Ctrl+C信号,转而传递给子进程,这样Go程序就能捕获信号并执行完整的关闭逻辑。
方法2:用CMD在当前窗口运行并等待
针对Windows原生CMD/PowerShell终端,无需绝对路径和修改build后缀,直接用start /wait并指定空窗口标题,就能在当前窗口运行并等待程序完成:
run: cls cmd /c start /wait "" ./bin/my-service
解释:
start /wait会等待程序完全退出后才返回- 空的
""是start命令的窗口标题参数,省略会导致把程序路径识别为标题,进而弹出新窗口 - Go在Windows下编译时,即使指定输出名为
my-service,实际会生成my-service.exe,CMD会自动识别并运行,无需手动加.exe后缀
方法3:PowerShell直接调用
如果默认终端是PowerShell,可直接用PowerShell命令调用程序,它会自动等待程序完成包括优雅关闭在内的所有流程:
run: cls powershell -Command "./bin/my-service; exit $LASTEXITCODE"
这样收到Ctrl+C时,PowerShell会把信号传递给Go程序,确保它执行完所有关闭逻辑后再退出。
内容的提问来源于stack exchange,提问作者Quintonn

