4.5.1 信号处理机制

4.5.1 信号处理机制 #

信号是 Unix/Linux 系统中进程间通信的一种异步机制,用于通知进程发生了某种事件。在系统编程中,正确处理信号对于构建稳定可靠的应用程序至关重要。Go 语言提供了完善的信号处理机制,让我们能够优雅地响应系统事件。

信号基础概念 #

常见信号类型 #

Unix/Linux 系统定义了多种信号,每种信号都有特定的含义和默认行为:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    // 常见信号的含义
    signals := map[os.Signal]string{
        syscall.SIGINT:  "中断信号 (Ctrl+C)",
        syscall.SIGTERM: "终止信号",
        syscall.SIGKILL: "强制终止信号 (不可捕获)",
        syscall.SIGQUIT: "退出信号 (Ctrl+\\)",
        syscall.SIGHUP:  "挂起信号",
        syscall.SIGUSR1: "用户自定义信号1",
        syscall.SIGUSR2: "用户自定义信号2",
        syscall.SIGPIPE: "管道破裂信号",
        syscall.SIGCHLD: "子进程状态改变信号",
    }

    fmt.Println("常见 Unix/Linux 信号:")
    for sig, desc := range signals {
        fmt.Printf("%-10s: %s\n", sig, desc)
    }
}

信号的分类 #

信号可以按照不同的标准进行分类:

package main

import (
    "fmt"
    "syscall"
)

func main() {
    // 按可靠性分类
    fmt.Println("=== 按可靠性分类 ===")

    // 不可靠信号 (1-31)
    unreliableSignals := []string{
        "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP",
        "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1",
        "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM",
    }

    fmt.Println("不可靠信号 (传统信号):")
    for i, sig := range unreliableSignals {
        fmt.Printf("  %d: %s\n", i+1, sig)
    }

    // 可靠信号 (34-64)
    fmt.Println("\n可靠信号 (实时信号):")
    fmt.Println("  SIGRTMIN ~ SIGRTMAX (34-64)")

    // 按处理方式分类
    fmt.Println("\n=== 按处理方式分类 ===")

    catchableSignals := []syscall.Signal{
        syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP,
        syscall.SIGUSR1, syscall.SIGUSR2,
    }

    fmt.Println("可捕获信号:")
    for _, sig := range catchableSignals {
        fmt.Printf("  %s\n", sig)
    }

    fmt.Println("\n不可捕获信号:")
    fmt.Printf("  %s (强制终止)\n", syscall.SIGKILL)
    fmt.Printf("  %s (强制停止)\n", syscall.SIGSTOP)
}

Go 语言信号处理 #

基本信号捕获 #

Go 语言通过 os/signal 包提供信号处理功能:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    // 创建信号通道
    sigChan := make(chan os.Signal, 1)

    // 注册要捕获的信号
    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

    fmt.Println("程序启动,按 Ctrl+C 发送 SIGINT 信号")
    fmt.Println("使用 kill 命令发送 SIGTERM 信号")

    // 模拟程序工作
    go func() {
        for {
            fmt.Printf("工作中... %s\n", time.Now().Format("15:04:05"))
            time.Sleep(2 * time.Second)
        }
    }()

    // 等待信号
    sig := <-sigChan
    fmt.Printf("\n接收到信号: %s\n", sig)
    fmt.Println("程序即将退出...")

    // 清理资源
    time.Sleep(1 * time.Second)
    fmt.Println("清理完成,程序退出")
}

多信号处理 #

在实际应用中,我们通常需要处理多种不同的信号:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

type SignalHandler struct {
    sigChan chan os.Signal
    done    chan bool
}

func NewSignalHandler() *SignalHandler {
    return &SignalHandler{
        sigChan: make(chan os.Signal, 1),
        done:    make(chan bool, 1),
    }
}

func (sh *SignalHandler) Start() {
    // 注册多个信号
    signal.Notify(sh.sigChan,
        syscall.SIGINT,  // Ctrl+C
        syscall.SIGTERM, // 终止信号
        syscall.SIGHUP,  // 挂起信号
        syscall.SIGUSR1, // 用户信号1
        syscall.SIGUSR2, // 用户信号2
    )

    go sh.handleSignals()
}

func (sh *SignalHandler) handleSignals() {
    for {
        select {
        case sig := <-sh.sigChan:
            switch sig {
            case syscall.SIGINT:
                fmt.Println("\n接收到 SIGINT (Ctrl+C),准备优雅关闭...")
                sh.gracefulShutdown()
                return

            case syscall.SIGTERM:
                fmt.Println("\n接收到 SIGTERM,立即关闭...")
                sh.immediateShutdown()
                return

            case syscall.SIGHUP:
                fmt.Println("\n接收到 SIGHUP,重新加载配置...")
                sh.reloadConfig()

            case syscall.SIGUSR1:
                fmt.Println("\n接收到 SIGUSR1,打印状态信息...")
                sh.printStatus()

            case syscall.SIGUSR2:
                fmt.Println("\n接收到 SIGUSR2,切换日志级别...")
                sh.toggleLogLevel()
            }

        case <-sh.done:
            return
        }
    }
}

func (sh *SignalHandler) gracefulShutdown() {
    fmt.Println("开始优雅关闭流程...")

    // 停止接收新请求
    fmt.Println("1. 停止接收新请求")
    time.Sleep(500 * time.Millisecond)

    // 等待现有请求完成
    fmt.Println("2. 等待现有请求完成")
    time.Sleep(1 * time.Second)

    // 关闭数据库连接
    fmt.Println("3. 关闭数据库连接")
    time.Sleep(300 * time.Millisecond)

    // 清理临时文件
    fmt.Println("4. 清理临时文件")
    time.Sleep(200 * time.Millisecond)

    fmt.Println("优雅关闭完成")
    sh.done <- true
}

func (sh *SignalHandler) immediateShutdown() {
    fmt.Println("立即关闭程序")
    sh.done <- true
}

func (sh *SignalHandler) reloadConfig() {
    fmt.Println("重新加载配置文件...")
    // 模拟配置重载
    time.Sleep(500 * time.Millisecond)
    fmt.Println("配置重载完成")
}

func (sh *SignalHandler) printStatus() {
    fmt.Printf("程序状态: 运行中, 时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
    fmt.Printf("Goroutine 数量: %d\n", runtime.NumGoroutine())
    fmt.Printf("内存使用: %.2f MB\n", float64(getMemUsage())/1024/1024)
}

func (sh *SignalHandler) toggleLogLevel() {
    fmt.Println("切换日志级别 (DEBUG <-> INFO)")
}

func (sh *SignalHandler) Wait() {
    <-sh.done
}

func main() {
    fmt.Println("多信号处理示例")
    fmt.Println("支持的信号:")
    fmt.Println("  SIGINT (Ctrl+C) - 优雅关闭")
    fmt.Println("  SIGTERM - 立即关闭")
    fmt.Println("  SIGHUP - 重载配置")
    fmt.Println("  SIGUSR1 - 打印状态")
    fmt.Println("  SIGUSR2 - 切换日志级别")
    fmt.Printf("进程 PID: %d\n", os.Getpid())

    handler := NewSignalHandler()
    handler.Start()

    // 模拟程序工作
    go func() {
        for {
            fmt.Printf("工作中... %s\n", time.Now().Format("15:04:05"))
            time.Sleep(3 * time.Second)
        }
    }()

    // 等待信号处理完成
    handler.Wait()
    fmt.Println("程序退出")
}

// 辅助函数
func getMemUsage() uint64 {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    return m.Alloc
}

优雅关闭模式 #

HTTP 服务器优雅关闭 #

在 Web 服务开发中,优雅关闭是一个重要的特性:

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

type GracefulServer struct {
    server   *http.Server
    shutdown chan struct{}
}

func NewGracefulServer(addr string) *GracefulServer {
    mux := http.NewServeMux()

    // 注册路由
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // 模拟处理时间
        time.Sleep(2 * time.Second)
        fmt.Fprintf(w, "Hello! Time: %s\n", time.Now().Format("15:04:05"))
    })

    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        fmt.Fprintf(w, "OK")
    })

    mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
        // 模拟慢请求
        time.Sleep(10 * time.Second)
        fmt.Fprintf(w, "Slow response completed\n")
    })

    server := &http.Server{
        Addr:    addr,
        Handler: mux,
    }

    return &GracefulServer{
        server:   server,
        shutdown: make(chan struct{}),
    }
}

func (gs *GracefulServer) Start() error {
    // 启动服务器
    go func() {
        log.Printf("HTTP 服务器启动在 %s", gs.server.Addr)
        if err := gs.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("服务器启动失败: %v", err)
        }
    }()

    // 设置信号处理
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

    // 等待信号
    sig := <-sigChan
    log.Printf("接收到信号 %s,开始优雅关闭...", sig)

    return gs.Shutdown()
}

func (gs *GracefulServer) Shutdown() error {
    // 创建关闭上下文,设置超时时间
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    // 关闭服务器
    log.Println("正在关闭 HTTP 服务器...")
    if err := gs.server.Shutdown(ctx); err != nil {
        log.Printf("服务器关闭失败: %v", err)
        return err
    }

    log.Println("HTTP 服务器已优雅关闭")
    close(gs.shutdown)
    return nil
}

func (gs *GracefulServer) Wait() {
    <-gs.shutdown
}

func main() {
    server := NewGracefulServer(":8080")

    fmt.Println("优雅关闭示例")
    fmt.Println("访问 http://localhost:8080/ 测试普通请求")
    fmt.Println("访问 http://localhost:8080/slow 测试慢请求")
    fmt.Println("按 Ctrl+C 触发优雅关闭")

    if err := server.Start(); err != nil {
        log.Fatalf("服务器错误: %v", err)
    }

    server.Wait()
    fmt.Println("程序完全退出")
}

数据库连接优雅关闭 #

在处理数据库连接时,优雅关闭同样重要:

package main

import (
    "context"
    "database/sql"
    "fmt"
    "log"
    "os"
    "os/signal"
    "sync"
    "syscall"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

type DatabaseManager struct {
    db       *sql.DB
    wg       sync.WaitGroup
    shutdown chan struct{}
    ctx      context.Context
    cancel   context.CancelFunc
}

func NewDatabaseManager(dsn string) (*DatabaseManager, error) {
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        return nil, err
    }

    // 配置连接池
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(5)
    db.SetConnMaxLifetime(5 * time.Minute)

    ctx, cancel := context.WithCancel(context.Background())

    return &DatabaseManager{
        db:       db,
        shutdown: make(chan struct{}),
        ctx:      ctx,
        cancel:   cancel,
    }, nil
}

func (dm *DatabaseManager) Start() {
    // 启动后台任务
    dm.wg.Add(1)
    go dm.backgroundTask()

    // 设置信号处理
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

    log.Println("数据库管理器启动")

    // 等待信号
    sig := <-sigChan
    log.Printf("接收到信号 %s,开始关闭数据库连接...", sig)

    dm.Shutdown()
}

func (dm *DatabaseManager) backgroundTask() {
    defer dm.wg.Done()

    ticker := time.NewTicker(5 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            dm.performDatabaseOperation()

        case <-dm.ctx.Done():
            log.Println("后台任务收到关闭信号")
            return
        }
    }
}

func (dm *DatabaseManager) performDatabaseOperation() {
    // 模拟数据库操作
    ctx, cancel := context.WithTimeout(dm.ctx, 3*time.Second)
    defer cancel()

    // 检查上下文是否已取消
    select {
    case <-dm.ctx.Done():
        log.Println("操作被取消")
        return
    default:
    }

    // 执行查询
    rows, err := dm.db.QueryContext(ctx, "SELECT 1")
    if err != nil {
        log.Printf("查询错误: %v", err)
        return
    }
    defer rows.Close()

    log.Println("数据库操作完成")
}

func (dm *DatabaseManager) Shutdown() {
    log.Println("开始关闭数据库管理器...")

    // 1. 取消所有上下文
    dm.cancel()

    // 2. 等待所有 goroutine 完成
    log.Println("等待后台任务完成...")
    done := make(chan struct{})
    go func() {
        dm.wg.Wait()
        close(done)
    }()

    // 设置超时
    select {
    case <-done:
        log.Println("所有后台任务已完成")
    case <-time.After(10 * time.Second):
        log.Println("等待超时,强制关闭")
    }

    // 3. 关闭数据库连接
    log.Println("关闭数据库连接...")
    if err := dm.db.Close(); err != nil {
        log.Printf("关闭数据库连接失败: %v", err)
    } else {
        log.Println("数据库连接已关闭")
    }

    close(dm.shutdown)
}

func (dm *DatabaseManager) Wait() {
    <-dm.shutdown
}

func main() {
    // 注意:这里使用模拟的 DSN,实际使用时需要替换为真实的数据库连接字符串
    dsn := "user:password@tcp(localhost:3306)/testdb"

    dm, err := NewDatabaseManager(dsn)
    if err != nil {
        log.Fatalf("创建数据库管理器失败: %v", err)
    }

    fmt.Println("数据库优雅关闭示例")
    fmt.Printf("进程 PID: %d\n", os.Getpid())
    fmt.Println("按 Ctrl+C 触发优雅关闭")

    dm.Start()
    dm.Wait()

    fmt.Println("程序完全退出")
}

信号处理最佳实践 #

信号处理器设计模式 #

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "os/signal"
    "sync"
    "syscall"
    "time"
)

// SignalHandler 接口定义
type SignalHandler interface {
    Handle(sig os.Signal) bool // 返回 true 表示继续运行,false 表示退出
}

// 应用程序结构
type Application struct {
    name     string
    handlers map[os.Signal]SignalHandler
    shutdown chan struct{}
    wg       sync.WaitGroup
    ctx      context.Context
    cancel   context.CancelFunc
}

func NewApplication(name string) *Application {
    ctx, cancel := context.WithCancel(context.Background())

    return &Application{
        name:     name,
        handlers: make(map[os.Signal]SignalHandler),
        shutdown: make(chan struct{}),
        ctx:      ctx,
        cancel:   cancel,
    }
}

// 注册信号处理器
func (app *Application) RegisterHandler(sig os.Signal, handler SignalHandler) {
    app.handlers[sig] = handler
}

// 启动应用程序
func (app *Application) Run() error {
    log.Printf("应用程序 %s 启动", app.name)

    // 收集所有注册的信号
    var signals []os.Signal
    for sig := range app.handlers {
        signals = append(signals, sig)
    }

    if len(signals) == 0 {
        return fmt.Errorf("没有注册任何信号处理器")
    }

    // 设置信号捕获
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, signals...)

    // 启动工作协程
    app.wg.Add(1)
    go app.worker()

    // 信号处理循环
    for {
        select {
        case sig := <-sigChan:
            log.Printf("接收到信号: %s", sig)

            if handler, exists := app.handlers[sig]; exists {
                if !handler.Handle(sig) {
                    log.Println("信号处理器请求退出")
                    app.Shutdown()
                    return nil
                }
            } else {
                log.Printf("未找到信号 %s 的处理器", sig)
            }

        case <-app.shutdown:
            log.Println("应用程序关闭")
            return nil
        }
    }
}

func (app *Application) worker() {
    defer app.wg.Done()

    ticker := time.NewTicker(2 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            log.Printf("%s 工作中...", app.name)

        case <-app.ctx.Done():
            log.Println("工作协程收到关闭信号")
            return
        }
    }
}

func (app *Application) Shutdown() {
    log.Println("开始关闭应用程序...")

    // 取消上下文
    app.cancel()

    // 等待工作协程完成
    done := make(chan struct{})
    go func() {
        app.wg.Wait()
        close(done)
    }()

    select {
    case <-done:
        log.Println("所有工作协程已完成")
    case <-time.After(5 * time.Second):
        log.Println("等待超时,强制关闭")
    }

    close(app.shutdown)
}

// 具体的信号处理器实现

// 优雅关闭处理器
type GracefulShutdownHandler struct{}

func (h *GracefulShutdownHandler) Handle(sig os.Signal) bool {
    log.Printf("执行优雅关闭处理 (信号: %s)", sig)
    return false // 请求退出
}

// 配置重载处理器
type ConfigReloadHandler struct {
    configFile string
}

func NewConfigReloadHandler(configFile string) *ConfigReloadHandler {
    return &ConfigReloadHandler{configFile: configFile}
}

func (h *ConfigReloadHandler) Handle(sig os.Signal) bool {
    log.Printf("重新加载配置文件: %s", h.configFile)
    // 模拟配置重载
    time.Sleep(500 * time.Millisecond)
    log.Println("配置重载完成")
    return true // 继续运行
}

// 状态报告处理器
type StatusReportHandler struct{}

func (h *StatusReportHandler) Handle(sig os.Signal) bool {
    log.Printf("生成状态报告 (信号: %s)", sig)

    // 打印系统状态
    fmt.Printf("=== 应用程序状态报告 ===\n")
    fmt.Printf("时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
    fmt.Printf("PID: %d\n", os.Getpid())
    fmt.Printf("Goroutine 数量: %d\n", runtime.NumGoroutine())

    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Printf("内存使用: %.2f MB\n", float64(m.Alloc)/1024/1024)
    fmt.Printf("========================\n")

    return true // 继续运行
}

func main() {
    app := NewApplication("SignalDemo")

    // 注册信号处理器
    app.RegisterHandler(syscall.SIGINT, &GracefulShutdownHandler{})
    app.RegisterHandler(syscall.SIGTERM, &GracefulShutdownHandler{})
    app.RegisterHandler(syscall.SIGHUP, NewConfigReloadHandler("/etc/app/config.yaml"))
    app.RegisterHandler(syscall.SIGUSR1, &StatusReportHandler{})

    fmt.Println("信号处理器设计模式示例")
    fmt.Printf("进程 PID: %d\n", os.Getpid())
    fmt.Println("支持的信号:")
    fmt.Println("  SIGINT/SIGTERM - 优雅关闭")
    fmt.Println("  SIGHUP - 重载配置")
    fmt.Println("  SIGUSR1 - 状态报告")

    if err := app.Run(); err != nil {
        log.Fatalf("应用程序运行失败: %v", err)
    }

    fmt.Println("应用程序退出")
}

小结 #

信号处理是系统编程中的重要技能。通过本节学习,我们掌握了:

  1. 信号基础概念:了解了各种信号的含义和分类
  2. Go 信号处理:学会使用 os/signal 包处理信号
  3. 优雅关闭模式:实现了 HTTP 服务器和数据库连接的优雅关闭
  4. 最佳实践:掌握了信号处理器的设计模式

正确的信号处理能够让我们的程序更加稳定可靠,特别是在生产环境中,这些技术是构建高质量系统服务的基础。