信号处理与Go程序的优雅退出

学过计算机系统的人,应该知道异常控制流(ECF)。异常控制流发生在计算机系统的各个层次。比如,在硬件层,硬件检测到的事件会触发控制突然转移到异常处理程序。在操作系统层,内核通过上下文切换将控制从一个用户进程转移到另一个用户进程。在应用层,一个进程可以发送信号到另一个进程,而接收者会将控制突然转移到它的一个信号处理程序。

什么是信号

本文讨论的就是在应用层次的异常,也被称为 信号 。我们知道,在正常情况下,低层次的硬件异常是由内核异常处理程序处理的,对用户进程而来是不可见的。而信号提供了一种机制,通知用户进程发生了这些异常。

简单来说,一个信号就是一条事件消息,它通知进程系统中发生了一个某种类型的事件。

举几个常见的例子(以linux为例)。SIGINT,它就是通过我们在终端输入的Ctrl+C进行触发;SIGKILL,即终端输入kill -9;SIGUSR1和SIGUSR2,用户自定义信号。

可以通过kill -l查看本机操作系统提供了哪些信号,以下是小菜刀mac上支持的信号列表。

$ kill -l
 1) SIGHUP   2) SIGINT   3) SIGQUIT   4) SIGILL
 5) SIGTRAP   6) SIGABRT   7) SIGEMT   8) SIGFPE
 9) SIGKILL  10) SIGBUS  11) SIGSEGV  12) SIGSYS
13) SIGPIPE  14) SIGALRM  15) SIGTERM  16) SIGURG
17) SIGSTOP  18) SIGTSTP  19) SIGCONT  20) SIGCHLD
21) SIGTTIN  22) SIGTTOU  23) SIGIO  24) SIGXCPU
25) SIGXFSZ  26) SIGVTALRM  27) SIGPROF  28) SIGWINCH
29) SIGINFO  30) SIGUSR1  31) SIGUSR2

(左右滑动查看完整代码图片)

如果你想知道每种信号,代表什么含义,可通过man signal命令查看信号注释。

$ man signal
 No    Name         Default Action       Description
 1     SIGHUP       terminate process    terminal line hangup
 2     SIGINT       terminate process    interrupt program
 3     SIGQUIT      create core image    quit program
 4     SIGILL       create core image    illegal instruction
 5     SIGTRAP      create core image    trace trap
 6     SIGABRT      create core image    abort program (formerly SIGIOT)
 7     SIGEMT       create core image    emulate instruction executed
 8     SIGFPE       create core image    floating-point exception
 9     SIGKILL      terminate process    kill program
 10    SIGBUS       create core image    bus error
 11    SIGSEGV      create core image    segmentation violation
 12    SIGSYS       create core image    non-existent system call invoked
 13    SIGPIPE      terminate process    write on a pipe with no reader
 14    SIGALRM      terminate process    real-time timer expired
 15    SIGTERM      terminate process    software termination signal
 16    SIGURG       discard signal       urgent condition present on socket
 17    SIGSTOP      stop process         stop (cannot be caught or ignored)
 18    SIGTSTP      stop process         stop signal generated from keyboard
 19    SIGCONT      discard signal       continue after stop
 20    SIGCHLD      discard signal       child status has changed
 21    SIGTTIN      stop process         background read attempted from control terminal
 22    SIGTTOU      stop process         background write attempted to control terminal
 23    SIGIO        discard signal       I/O is possible on a descriptor (see fcntl(2))
 24    SIGXCPU      terminate process    cpu time limit exceeded (see setrlimit(2))
 25    SIGXFSZ      terminate process    file size limit exceeded (see setrlimit(2))
 26    SIGVTALRM    terminate process    virtual time alarm (see setitimer(2))
 27    SIGPROF      terminate process    profiling timer alarm (see setitimer(2))
 28    SIGWINCH     discard signal       Window size change
 29    SIGINFO      discard signal       status request from keyboard
 30    SIGUSR1      terminate process    User defined signal 1
 31    SIGUSR2      terminate process    User defined signal 2

(左右滑动查看完整代码图片)

注意:SIGKILL和SIGSTOP这两个信号既不能被应用程序捕获,也不能被操作系统阻塞或忽略,这意味着当你在使用kill -9的时候一定要十分的慎重!

信号监听

Go中对信号的监听主要通过os/sgnal包下的四个方法

  • Notify:监听信号

  • Stop:取消监听

  • Reset:重置监听

  • Ignore/Ignored:忽略信号

监听示例代码

func signalHandler() {
  signalChan := make(chan os.Signal, 1)
  signal.Notify(signalChan) // 监听所有信号。注意,Notify接收可变参数,可以指定监听信号。


  go func() {
    for {
      sig := <-signalChan // 监听到信号
      log.Printf("got signal to exit [signal = %v]", sig)
    }
  }()
}


func main() {
  signalHandler()
  select {}
}

(左右滑动查看完整代码图片)

在goland中执行代码输入示例

2020/05/31 11:28:31 got signal to exit [signal = window size changes]
2020/05/31 11:28:31 got signal to exit [signal = urgent I/O condition]
2020/05/31 11:28:31 got signal to exit [signal = window size changes]
2020/05/31 11:28:32 got signal to exit [signal = interrupt]


Process finished with exit code 9

(左右滑动查看完整代码图片)

注意:在该代码中,你再也不能通过Ctrl+C关闭程序了(上述输出第四行代表的就是接收到SIGINT信号),你只能采用kill -9的方式关闭(上述最后一行代表的就是接收到SIGKILL信号)。

当然,你也可以指定监听特定信号。

signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)

需要注意的是,不同系统平台的信号定义会有所差别,可在syscall包下zerrors_xxx_yyy.go中找到对应信号(其中,xxx代表操作系统,yyy代表硬件体系。例如小菜刀的机器上对应的就是zerrors_darwin_amd64.go文件中的信号定义)。

Go程序优雅退出

我们都知道,应用程序在部署上线后,都会遇到升级维护的问题,一般需要停掉应用程序,再更新代码,启动程序。但是,我们在停掉线上运行着的应用程序时,通常不能简单粗暴的直接kill,需要做一些退出前处理(例如关掉数据库连接,持久化日志,清理应用垃圾等),而退出处理的触发就是通过信号接收处理。

因此,程序的优雅退出即是,在程序退出之前,相关资源得到妥善地处理。以下为优雅退出示例代码

func SetupSignalHandler(shutdownFunc func(bool)) {
  closeSignalChan := make(chan os.Signal, 1)
  // 监听四种关闭信号
  signal.Notify(closeSignalChan,
    syscall.SIGHUP,
    syscall.SIGINT,
    syscall.SIGTERM,
    syscall.SIGQUIT)


  go func() {
    sig := <-closeSignalChan
    log.Printf("got signal to exit [signal = %v]", sig)
    //判断关闭信号是否为SIGQUIT(用户发送Ctrl+/即可触发)
    shutdownFunc(sig == syscall.SIGQUIT)
  }()
}


func shutdown(isgraceful bool) {
  if isgraceful {
    //当满足 sig == syscall.SIGQUIT,做相应退出处理
  }
  // 不是syscall.SIGQUIT的退出信号时,做相应退出处理
}


func main() {
  SetupSignalHandler(shutdown) // 注册监听信号,绑定信号处理机制
  select {} // 模拟应用程序一直保持在线运行
}

(左右滑动查看完整代码图片)

参考文章

1. 《深入理解计算机系统 第三版》

2.  https://golang.google.cn/pkg/os/signal/

Golang技术分享

长按识别二维码关注我们

Golang相关学习及视频资源请回复公众号

1024

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值