Go 实现程序优雅退出

在 Go 语言中,优雅退出程序需要确保程序在收到终止信号(如 SIGINTSIGTERM)时,能安全完成资源清理(如关闭数据库连接、释放文件句柄、保存状态等),再退出。以下是实现优雅退出的完整方案:


1. 核心实现步骤

(1) 监听系统信号

使用 os/signal 包监听操作系统信号(如 Ctrl+C 触发 SIGINT)。

(2) 定义清理函数

编写资源释放逻辑,例如关闭数据库连接、停止后台协程等。

(3) 同步等待

通过 sync.WaitGroupcontext.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. 常见问题

  1. 协程无法退出?
    • 确保协程监听 ctx.Done() 或通过通道传递退出信号。
    • 避免协程中存在无法中断的阻塞操作(如无超时的 channel 读取)。
  2. 超时时间如何设置?
    • 根据业务容忍度调整(如 HTTP 服务设为 5~30 秒)。
  3. 如何处理强制终止(如 kill -9)?
    • SIGKILL 无法捕获,需依赖外部监控或定期持久化状态。

通过结合 contextsync.WaitGroup 和信号监听,可以实现安全、可控的优雅退出机制。

posted @   搁浅~浅浅浅  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示