Golang信号处理实战

Written by

in

文章目录
  • 在类 Unix 系统中,信号(Signal)是一种异步通知机制,内核通过它告诉进程发生了某种事件,比如: 终止进程:SIGTERM(kill 发送的默认信号)、SIGINT(Ctrl+C) 挂起/恢复:SIGTSTP(Ctrl+Z) 重新加载配置:SIGHUP 自定义信号:SIGUSR1、SIGUSR2 如果不处理,进程会使用 默认行为(可能直接退出)。而 os/signal 包让我们在用户态捕获这些信号,并执行自定义逻辑(比如优雅退出、保存状态、重载配置等)。
  • 函数 功能 常见用途 Notify(c chan<- os.Signal, sig …os.Signal) 将指定信号转发到 c 订阅信号 Stop(c chan<- os.Signal) 停止向 c 转发信号 取消订阅 Ignore(sig …os.Signal) 忽略信号(不再转发给程序) 屏蔽特定信号 Reset(sig …os.Signal) 恢复信号默认行为 信号处理恢复默认 NotifyContext(ctx, sig…) 返回会在收到信号时自动 cancel 的 Context 优雅退出
  • Go 1.16+ 引入的 NotifyContext 结合 context 让信号处理更简洁。 package main import ( “context” “fmt” “os/signal” “syscall” “time” ) func main() { ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() fmt.Println(“程序启动,等待信号…”) // 模拟业务协程 go func() { for { select { case <-ctx.Done(): fmt.Println(“业务收到退出信号,清理中…”) time.Sleep(1 * time.Second) fmt.Println(“业务清理完成”) return default: fmt.Println(“业务运行中…”) time.Sleep(2 * time.Second) } } }() <-ctx.Done() // 阻塞,直到信号触发 fmt.Println(“主程序退出”) } 好处: 自动取消 context 不用自己建 channel 多个 goroutine 可同时感知退出
  • 简化版流程: Notify 注册信号 → 调用 runtime 的 enableSignal(n)。 runtime 捕获信号后调用 process()。 process 遍历所有 channel handler,非阻塞发送信号。 Stop 时调用 disableSignal(n),等待 runtime 信号队列清空(signalWaitUntilIdle())。 特点: 非阻塞投递:channel 必须有缓冲,否则可能丢信号。 引用计数:多个 channel 可监听同一信号,ref=0 时才会真正停止捕获。 bitmask 存储:handler 用 bit 位记录关注的信号,内存占用小。
  • 总是用缓冲 channel make(chan os.Signal, 1) 避免信号丢失。 优雅退出而不是强杀在 SIGTERM 里做清理,配合 context 实现安全收尾。 避免监听全部信号只订阅需要的信号,避免影响系统默认行为。 多 goroutine 协同用 NotifyContext 让所有协程通过 <-ctx.Done() 感知退出。 容器化部署必备Docker 默认用 SIGTERM 停止容器,业务代码应处理此信号。
  • package main import ( “context” “fmt” “net/http” “os/signal” “syscall” “time” ) func main() { srv := &http.Server{Addr: “:8080”} // 启动 HTTP 服务 go func() { fmt.Println(“HTTP 服务启动在 :8080”) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { fmt.Println(“HTTP 服务器出错:”, err) } }() // 信号监听 ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() <-ctx.Done() // 等待信号 fmt.Println(“收到退出信号,正在关闭服务器…”) // 设置超时的优雅关闭 shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil { fmt.Println(“服务器关闭错误:”, err) } fmt.Println(“服务器已优雅退出”) } 这样写的好处: 支持 Ctrl+C / kill 容器化部署时能优雅退出 确保连接处理完成后再关闭 到此这篇关于Golang信号处理实战的文章就介绍到这了,更多相关Golang信号处理内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: Golang中的信号(Signal)机制详解 拦截信号Golang应用优雅关闭的操作方法 Golang信号量设计实现示例详解 golang 监听服务的信号,实现平滑启动,linux信号说明详解 golang使用信号量热更新的实现示例 Golang信号处理及如何实现进程的优雅退出详解 golang模拟实现带超时的信号量示例代码
  • 目录
    • 1. 为什么需要信号处理
    • 2. 核心 API
    • 3. 基本使用
      • 示例:监听SIGINT(Ctrl+C)和SIGTERM(kill)
    • 4. 使用NotifyContext优雅退出
      • 5. 高级用法
        • 5.1 忽略信号
        • 5.2 动态取消订阅
        • 5.3 同时监听多个信号
      • 6. 原理机制
        • 7. 最佳实践
          • 8. 实战案例:优雅关闭 HTTP 服务器

            在类 Unix 系统中,信号(Signal)是一种异步通知机制,内核通过它告诉进程发生了某种事件,比如:

            • 终止进程SIGTERM(kill 发送的默认信号)、SIGINT(Ctrl+C)
            • 挂起/恢复SIGTSTP(Ctrl+Z)
            • 重新加载配置SIGHUP
            • 自定义信号SIGUSR1SIGUSR2

            如果不处理,进程会使用 默认行为(可能直接退出)。
            os/signal 包让我们在用户态捕获这些信号,并执行自定义逻辑(比如优雅退出、保存状态、重载配置等)。

            函数 功能 常见用途
            Notify(c chan<- os.Signal, sig …os.Signal) 将指定信号转发到 c 订阅信号
            Stop(c chan<- os.Signal) 停止向 c 转发信号 取消订阅
            Ignore(sig …os.Signal) 忽略信号(不再转发给程序) 屏蔽特定信号
            Reset(sig …os.Signal) 恢复信号默认行为 信号处理恢复默认
            NotifyContext(ctx, sig…) 返回会在收到信号时自动 cancel 的 Context 优雅退出

            package main
            
            import (
            	"fmt"
            	"os"
            	"os/signal"
            	"syscall"
            )
            
            func main() {
            	sigChan := make(chan os.Signal, 1)
            
            	// 订阅两个信号
            	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
            
            	fmt.Println("程序启动,等待信号...")
            
            	sig := <-sigChan // 阻塞等待
            	fmt.Println("收到信号:", sig)
            
            	fmt.Println("执行清理逻辑...")
            	// 这里做关闭文件、断开连接等操作
            
            	fmt.Println("程序退出")
            }
            

            运行:

            go run main.go
            # Ctrl+C 或 kill PID 会触发信号
            

            Go 1.16+ 引入的 NotifyContext 结合 context 让信号处理更简洁。

            package main
            
            import (
            	"context"
            	"fmt"
            	"os/signal"
            	"syscall"
            	"time"
            )
            
            func main() {
            	ctx, stop := signal.NotifyContext(context.Background(),
            		syscall.SIGINT, syscall.SIGTERM)
            	defer stop()
            
            	fmt.Println("程序启动,等待信号...")
            
            	// 模拟业务协程
            	go func() {
            		for {
            			select {
            			case <-ctx.Done():
            				fmt.Println("业务收到退出信号,清理中...")
            				time.Sleep(1 * time.Second)
            				fmt.Println("业务清理完成")
            				return
            			default:
            				fmt.Println("业务运行中...")
            				time.Sleep(2 * time.Second)
            			}
            		}
            	}()
            
            	<-ctx.Done() // 阻塞,直到信号触发
            	fmt.Println("主程序退出")
            }
            

            好处:

            • 自动取消 context
            • 不用自己建 channel
            • 多个 goroutine 可同时感知退出

            signal.Ignore(syscall.SIGPIPE) // 忽略管道断开
            

            signal.Stop(sigChan) // 取消 channel 的订阅
            

            signal.Notify(sigChan) // 不指定信号时,监听所有信号
            

            不推荐监听全部信号,可能会拦截 SIGKILL、SIGSTOP 等无法处理的信号。

            简化版流程:

            1. Notify 注册信号 → 调用 runtime 的 enableSignal(n)
            2. runtime 捕获信号后调用 process()
            3. process 遍历所有 channel handler,非阻塞发送信号。
            4. Stop 时调用 disableSignal(n),等待 runtime 信号队列清空(signalWaitUntilIdle())。

            特点:

            • 非阻塞投递:channel 必须有缓冲,否则可能丢信号。
            • 引用计数:多个 channel 可监听同一信号,ref=0 时才会真正停止捕获。
            • bitmask 存储:handler 用 bit 位记录关注的信号,内存占用小。

            1. 总是用缓冲 channel

              make(chan os.Signal, 1)
              

              避免信号丢失。

            2. 优雅退出而不是强杀
              SIGTERM 里做清理,配合 context 实现安全收尾。

            3. 避免监听全部信号
              只订阅需要的信号,避免影响系统默认行为。

            4. 多 goroutine 协同
              NotifyContext 让所有协程通过 <-ctx.Done() 感知退出。

            5. 容器化部署必备
              Docker 默认用 SIGTERM 停止容器,业务代码应处理此信号。

            package main
            
            import (
            	"context"
            	"fmt"
            	"net/http"
            	"os/signal"
            	"syscall"
            	"time"
            )
            
            func main() {
            	srv := &http.Server{Addr: ":8080"}
            
            	// 启动 HTTP 服务
            	go func() {
            		fmt.Println("HTTP 服务启动在 :8080")
            		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            			fmt.Println("HTTP 服务器出错:", err)
            		}
            	}()
            
            	// 信号监听
            	ctx, stop := signal.NotifyContext(context.Background(),
            		syscall.SIGINT, syscall.SIGTERM)
            	defer stop()
            
            	<-ctx.Done() // 等待信号
            	fmt.Println("收到退出信号,正在关闭服务器...")
            
            	// 设置超时的优雅关闭
            	shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
            	defer cancel()
            
            	if err := srv.Shutdown(shutdownCtx); err != nil {
            		fmt.Println("服务器关闭错误:", err)
            	}
            
            	fmt.Println("服务器已优雅退出")
            }
            

            这样写的好处:

            • 支持 Ctrl+C / kill
            • 容器化部署时能优雅退出
            • 确保连接处理完成后再关闭

            到此这篇关于Golang信号处理实战的文章就介绍到这了,更多相关Golang信号处理内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

            您可能感兴趣的文章:

            • Golang中的信号(Signal)机制详解
            • 拦截信号Golang应用优雅关闭的操作方法
            • Golang信号量设计实现示例详解
            • golang 监听服务的信号,实现平滑启动,linux信号说明详解
            • golang使用信号量热更新的实现示例
            • Golang信号处理及如何实现进程的优雅退出详解
            • golang模拟实现带超时的信号量示例代码

            站内搜索