Go 实现程序优雅退出
在 Go 语言中,优雅退出程序需要确保程序在收到终止信号(如 SIGINT
或 SIGTERM
)时,能安全完成资源清理(如关闭数据库连接、释放文件句柄、保存状态等),再退出。以下是实现优雅退出的完整方案:
1. 核心实现步骤
(1) 监听系统信号
使用 os/signal
包监听操作系统信号(如 Ctrl+C
触发 SIGINT
)。
(2) 定义清理函数
编写资源释放逻辑,例如关闭数据库连接、停止后台协程等。
(3) 同步等待
通过 sync.WaitGroup
或 context.Context
等待所有协程完成清理。
(4) 超时控制
设置超时机制,避免程序无限期等待。
2. 完整代码示例
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"
)
func main() {
// 初始化资源(模拟数据库连接、HTTP 服务等)
db := initDB()
defer db.Close() // 确保最终关闭
server := &http.Server{Addr: ":8080", Handler: http.DefaultServeMux}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
// 使用 WaitGroup 等待协程退出
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(context.Background())
// 启动 HTTP 服务
wg.Add(1)
go func() {
defer wg.Done()
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("HTTP server error: %v", err)
}
log.Println("HTTP server stopped")
}()
// 启动后台任务(模拟持续运行的工作协程)
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-ctx.Done():
log.Println("Background task stopped")
return
default:
// 模拟后台工作
log.Println("Working...")
time.Sleep(1 * time.Second)
}
}
}()
// 监听终止信号
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
<-signalChan // 阻塞等待信号
log.Println("Shutting down gracefully...")
// 触发资源清理
cancel() // 通知所有使用 ctx 的协程停止
// 关闭 HTTP 服务(设置超时)
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer shutdownCancel()
if err := server.Shutdown(shutdownCtx); err != nil {
log.Printf("HTTP server shutdown error: %v", err)
}
// 等待所有协程退出(设置超时)
done := make(chan struct{})
go func() {
wg.Wait() // 等待所有协程完成
close(done)
}()
select {
case <-done:
log.Println("All tasks finished")
case <-time.After(10 * time.Second):
log.Println("Timeout: forced termination")
}
log.Println("Exiting")
}
// 模拟数据库连接
type Database struct{}
func initDB() *Database {
log.Println("Database connected")
return &Database{}
}
func (db *Database) Close() {
log.Println("Database connection closed")
}
3. 关键点解析
(1) 信号监听
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
<-signalChan // 阻塞直到收到信号
- 监听
SIGINT
(Ctrl+C)和SIGTERM
(kill 命令)。
(2) 资源清理
-
HTTP 服务优雅关闭:
server.Shutdown(shutdownCtx) // 停止接受新请求,等待现有请求完成
-
协程管理:
ctx, cancel := context.WithCancel(context.Background()) cancel() // 通知所有依赖 ctx 的协程退出
(3) 同步等待
var wg sync.WaitGroup
wg.Add(1) // 协程启动前增加计数
go func() {
defer wg.Done() // 协程退出时减少计数
// ...
}()
wg.Wait() // 等待所有协程完成
(4) 超时控制
select {
case <-done: // 正常完成
case <-time.After(10 * time.Second): // 超时强制退出
}
- 避免清理任务因阻塞导致程序无法退出。
4. 运行效果
# 启动程序
$ go run main.go
Database connected
Working...
Working...
# 按下 Ctrl+C 后输出
Shutting down gracefully...
HTTP server stopped
Background task stopped
All tasks finished
Database connection closed
Exiting
5. 适用场景
- Web 服务:确保 HTTP 请求处理完成后再退出。
- 后台任务:如定时任务、消息队列消费者。
- 资源密集型应用:如数据库连接池、文件句柄释放。
6. 常见问题
- 协程无法退出?
- 确保协程监听
ctx.Done()
或通过通道传递退出信号。 - 避免协程中存在无法中断的阻塞操作(如无超时的
channel
读取)。
- 确保协程监听
- 超时时间如何设置?
- 根据业务容忍度调整(如 HTTP 服务设为 5~30 秒)。
- 如何处理强制终止(如
kill -9
)?SIGKILL
无法捕获,需依赖外部监控或定期持久化状态。
通过结合 context
、sync.WaitGroup
和信号监听,可以实现安全、可控的优雅退出机制。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具