Golang信号处理实战

 更新时间:2025年12月24日 11:23:46   作者:言之。  
本文介绍了Go语言中os/signal包的基本用法,用于处理Unix系统中的异步信号,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1. 为什么需要信号处理

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

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

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

2. 核心 API

函数功能常见用途
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优雅退出

3. 基本使用

示例:监听SIGINT(Ctrl+C)和SIGTERM(kill)

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 会触发信号

4. 使用NotifyContext优雅退出

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 可同时感知退出

5. 高级用法

5.1 忽略信号

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

5.2 动态取消订阅

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

5.3 同时监听多个信号

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

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

6. 原理机制

简化版流程:

  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 位记录关注的信号,内存占用小。

7. 最佳实践

  1. 总是用缓冲 channel

    make(chan os.Signal, 1)
    

    避免信号丢失。

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

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

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

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

8. 实战案例:优雅关闭 HTTP 服务器

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并发编程深入分析

    Golang并发编程深入分析

    golang中的并发,是函数相互独立运行的能力,goroutines是并发运行的函数。golang提供了goroutines作为并发处理的一种方式
    2022-11-11
  • Go+Lua解决Redis秒杀中库存与超卖问题

    Go+Lua解决Redis秒杀中库存与超卖问题

    本文主要介绍了Go+Lua解决Redis秒杀中库存与超卖问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • 简单易用的Go逗号comma ok模式使用详解

    简单易用的Go逗号comma ok模式使用详解

    这篇文章主要为大家介绍了简单易用的Go逗号comma ok模式使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • golang判断chan channel是否关闭的方法

    golang判断chan channel是否关闭的方法

    这篇文章主要介绍了golang判断chan channel是否关闭的方法,结合实例形式对比分析了Go语言判断chan没有关闭的后果及关闭的方法,需要的朋友可以参考下
    2016-07-07
  • golang一些常用的静态检查工具详解

    golang一些常用的静态检查工具详解

    这篇文章主要介绍了golang一些常用的静态检查工具,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • Go语言数据结构之选择排序示例详解

    Go语言数据结构之选择排序示例详解

    这篇文章主要为大家介绍了Go语言数据结构之选择排序示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • go语言写的简要数据同步工具详解

    go语言写的简要数据同步工具详解

    作为go-etl工具的作者,想要安利一下这个小巧的数据同步工具,它在同步百万级别的数据时表现极为优异,基本能在几分钟完成数据同步,这篇文章主要介绍了go语言写的简要数据同步工具,需要的朋友可以参考下
    2024-07-07
  • Go语言Goroutines 泄漏场景与防治解决分析

    Go语言Goroutines 泄漏场景与防治解决分析

    这篇文章主要为大家介绍了Go语言Goroutines 泄漏场景与防治解决分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • Go 语言中的 Struct Tag 的用法详解

    Go 语言中的 Struct Tag 的用法详解

    在 Go 语言中,结构体字段标签(Struct Tag) 是一种用于给字段添加元信息(metadata)的机制,常用于序列化(如 JSON、XML)、ORM 映射、验证等场景,本文给大家介绍Go 语言中的 Struct Tag 的用法,感兴趣的朋友一起看看吧
    2025-05-05
  • go-micro集成RabbitMQ实战和原理详解

    go-micro集成RabbitMQ实战和原理详解

    本文主要介绍go-micro使用RabbitMQ收发数据的方法和原理,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05

最新评论