前言

信号(Signal)是Linux, 类Unix和其它POSIX兼容的操作系统中用来进程间通讯的一种方式。对于Linux系统来说,信号就是软中断,用来通知进程发生了异步事件。

当信号发送到某个进程中时,操作系统会中断该进程的正常流程,并进入相应的信号处理函数执行操作,完成后再回到中断的地方继续执行。

有时候我们想在Go程序中处理Signal信号,比如收到SIGTERM信号后优雅的关闭程序,以及 goroutine结束通知等。

Go 语言提供了对信号处理的包(os/signal)。

Go 中对信号的处理主要使用os/signal包中的两个方法:一个是notify方法用来监听收到的信号;一个是 stop方法用来取消监听。

Go信号通知机制可以通过往一个channel中发送os.Signal实现。

信号类型

个平台的信号定义或许有些不同。下面列出了POSIX中定义的信号。
Linux 使用34-64信号用作实时系统中。
命令 man signal 提供了官方的信号介绍。
在POSIX.1-1990标准中定义的信号列表
信号动作说明
SIGHUP1Term终端控制进程结束(终端连接断开)
SIGINT2Term用户发送INTR字符(Ctrl+C)触发
SIGQUIT3Core用户发送QUIT字符(Ctrl+/)触发
SIGILL4Core非法指令(程序错误、试图执行数据段、栈溢出等)
SIGABRT6Core调用abort函数触发
SIGFPE8Core算术运行错误(浮点运算错误、除数为零等)
SIGKILL9Term无条件结束程序(不能被捕获、阻塞或忽略)
SIGSEGV11Core无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作)
SIGPIPE13Term消息管道损坏(FIFO/Socket通信时,管道未打开而进行写操作)
SIGALRM14Term时钟定时信号
SIGTERM15Term结束程序(可以被捕获、阻塞或忽略)
SIGUSR130,10,16Term用户保留
SIGUSR231,12,17Term用户保留
SIGCHLD20,17,18Ign子进程结束(由父进程接收)
SIGCONT19,18,25Cont继续执行已经停止的进程(不能被阻塞)
SIGSTOP17,19,23Stop停止进程(不能被捕获、阻塞或忽略)
SIGTSTP18,20,24Stop停止进程(可以被捕获、阻塞或忽略)
SIGTTIN21,21,26Stop后台程序从终端中读取数据时触发
SIGTTOU22,22,27Stop后台程序向终端中写数据时触发
  • 在SUSv2和POSIX.1-2001标准中的信号列表:
信号动作说明
SIGTRAP5CoreTrap指令触发(如断点,在调试器中使用)
SIGBUS0,7,10Core非法地址(内存地址对齐错误)
SIGPOLLTermPollable event (Sys V). Synonym for SIGIO
SIGPROF27,27,29Term性能时钟信号(包含系统调用时间和进程占用CPU的时间)
SIGSYS12,31,12Core无效的系统调用(SVr4)
SIGURG16,23,21Ign有紧急数据到达Socket(4.2BSD)
SIGVTALRM26,26,28Term虚拟时钟信号(进程占用CPU的时间)(4.2BSD)
SIGXCPU24,24,30Core超过CPU时间资源限制(4.2BSD)
SIGXFSZ25,25,31Core超过文件大小资源限制(4.2BSD)
第1列为信号名;
第2列为对应的信号值,需要注意的是,有些信号名对应着3个信号值,这是因为这些信号值与平台相关,将man手册中对3个信号值的说明摘出如下,the first one is usually valid for alpha and sparc, the middle one for i386, ppc and sh, and the last one for mips.
第3列为操作系统收到信号后的动作,Term表明默认动作为终止进程,Ign表明默认动作为忽略该信号,Core表明默认动作为终止进程同时输出core dump,Stop表明默认动作为停止进程。
第4列为对信号作用的注释性说明,浅显易懂,这里不再赘述。
需要特别说明的是,SIGKILL和SIGSTOP这两个信号既不能被应用程序捕获,也不能被操作系统阻塞或忽略。

Notify 示例

新建一个测试文件 test5.go


package main

import "fmt"
import "os"
import "os/signal"
import "syscall"

func main() {

   // 创建一个os.Signal channel
   sigs := make(chan os.Signal, 1)
   //创建一个bool channel
   done := make(chan bool, 1)

	//注册要接收的信号,syscall.SIGINT:接收ctrl+c ,syscall.SIGTERM:程序退出
	//信号没有信号参数表示接收所有的信号
   signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

     //此goroutine为执行阻塞接收信号。一旦有了它,它就会打印出来。
    //然后通知程序可以完成。
   go func() {
      sig := <-sigs
      fmt.Println(sig)
      done <- true
   }()

   //程序将在此处等待,直到它预期信号(如Goroutine所示)
   //在“done”上发送一个值,然后退出。
   fmt.Println("awaiting signal")
   <-done
   fmt.Println("exiting")
}

执行 go run test5.go 再敲入 ctrl+c,程序会输出

awaiting signal

interrupt
exiting

如果用kill pid 结束程序,则输出

awaiting signal
Terminated

取消监听 stop

修改代码,增加 signal.Stop(sigs)

package main

import "fmt"
import "os"
import "os/signal"
import "syscall"

func main() {

   // 创建一个os.Signal channel
   sigs := make(chan os.Signal, 1)
   //创建一个bool channel
   done := make(chan bool, 1)
   //注册要接收的信号,syscall.SIGINT:接收ctrl+c ,syscall.SIGTERM:程序退出
   //信号没有信号参数表示接收所有的信号
   signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

   //此goroutine为执行阻塞接收信号。一旦有了它,它就会打印出来。
   //然后通知程序可以完成。
   go func() {
      sig := <-sigs
      fmt.Println(sig)
      done <- true
   }()
   //不允许继续往sigs中存入内容
   signal.Stop(sigs)
   //程序将在此处等待,直到它预期信号(如Goroutine所示)
   //在“done”上发送一个值,然后退出。
   fmt.Println("awaiting signal")
   <-done
   fmt.Println("exiting")
}

执行 go run test5.go 再敲入 ctrl+c,程序输出仅awaiting signal。不会输出signal.Stop(sigs)之后的内容。

Go 优雅重启

如何实现优雅地重启一个Go网络程序呢。主要要解决两个问题:

1、重启进程不需要关闭端口。

2、保持已有的连接请求不中断,让其执行完成或超时。

大概个执行步骤:

  • fork一个继承侦听套接字的新进程。

  • 子进程初始化并开始接受套接字上的连接。

  • 子进程向父进程发信号,通知父进程停止接收连接并终止。

具体实现请阅读文章Graceful Restart in Golang中提供了一种方式。

参考

https://golang.org/pkg/os/signal/

https://grisha.org/blog/2014/06/03/graceful-restart-in-golang/

https://play.golang.org/p/BlkqAtKsxo

links

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐