Go程序如何优雅退出
目录
背景
很多人觉得“程序退出就是停掉进程”,但对后端服务来说,粗暴退出会引发一系列问题。先明确两个关键概念:
- 粗暴退出:用
kill -9、断电、进程崩溃等方式强制终止程序,会导致正在执行的任务中断、数据库连接未关闭、文件句柄泄露,甚至数据一致性问题(比如支付场景的“扣钱未到账”); - 优雅退出:收到退出信号后,程序主动执行“停止接收新请求→等待正在处理的请求完成→释放资源(关闭连接/文件)→终止进程”的流程,全程无数据丢失、无资源泄露。
优雅的退出 Go 程序:需要先关闭数据库连接、关闭 redis 连接、等待协程执行、清理数据等等…
实现
通过监听程序信号 ctrl + c 优雅退出,代码实现如下:
package main
import (
"log"
"os"
"os/signal"
"syscall"
)
func main() {
//TODO 执行业务代码
//监听退出序号
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sigs
log.Println(sig)
done <- true
}()
log.Println("Server Start Awaiting Signal")
<-done
// 执行清理操作
cleanup()
log.Println("Exiting")
}原理
操作系统信号机制
┌───────────────┐ ┌───────────────┐
│ 用户按下Ctrl+C │─────>│ 生成SIGINT信号 │
└───────────────┘ └───────────────┘
↓
┌───────────────────────────────┐
│ Go程序通过signal.Notify捕获信号 │
└───────────────────────────────┘| 信号名称 | 默认行为 | 触发场景 | Go可否捕获 |
|---|---|---|---|
| SIGINT | 终止进程 | Ctrl+C / kill -2 | ✅ |
| SIGTERM | 终止进程 | kill默认 / 容器停止 | ✅ |
| SIGKILL | 强制终止 | kill -9 / 系统OOM | ❌ |
| SIGHUP | 终止进程 | 终端断开 / 守护进程重载配置 | ✅ |
| SIGQUIT | 核心转储 | Ctrl+\ | ✅ |
常见问题
Q1、为什么有时候捕获不到?
操作系统强制终止进程 / 内存溢出被容器强制终止,不经过应用程序处理流程
Q2、协程里有无限循环(比如for { … }),怎么触发退出?
必须在循环里加入select { case <-ctx.Done(): return; default: ... },让协程能监听退出信号
如果大家在生产环境遇到特殊的退出场景(比如分布式服务退出),欢迎在评论区交流!
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/go-signal-exist/
备用原文链接: https://blog.fiveyoboy.com/articles/go-signal-exist/