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("应用程序退出")
}
小结 #
信号处理是系统编程中的重要技能。通过本节学习,我们掌握了:
- 信号基础概念:了解了各种信号的含义和分类
- Go 信号处理:学会使用
os/signal
包处理信号 - 优雅关闭模式:实现了 HTTP 服务器和数据库连接的优雅关闭
- 最佳实践:掌握了信号处理器的设计模式
正确的信号处理能够让我们的程序更加稳定可靠,特别是在生产环境中,这些技术是构建高质量系统服务的基础。