一个在命令行( 终端 )中运行的 Go 程序,无时不刻需要随时接收 CTRL+C 或者 kill
或 kill -9
等信号。
最常见的,就是使用 CTRL+C 组合键来关闭正在运行的程序。这个组合键会产生一个 SIGINT
信号。应用程序可以在收到这个信号时做一些清理工作,然后停止执行并退出。
Go 语言提供了 os/signal
包来处理发送给 Go 语言程序的信息,该包的官方文档为 https://godoc.org/os/signal ,文档很长,有空我们再介绍。今天我们只会使用到它的 signal.Notify
方法。
signal.Notify() 方法
signal.Notify()
方法的原型如下
func Notify(c chan<- os.Signal, sig ...os.Signal)
该方法会捕获所有发给该应用程序的信号,并把感兴趣的信号 sig
参数发送到管道 c
中。如果没有提供 sig
参数,那么该方法会把所有捕获的信号都发送给管道 c
。
而所有可以捕获的信号都以常量的形式定义在 os
包中,比如常见的 os.Interrupt
。
使用步骤
signal.Notify
方法的使用步骤有四步,我们以捕获 Ctrl + C 信号为例子
-
创建一个 os.Signal 类型的管道,长度为 1 。长度可以根据自己需要自定义
c := make(chan os.Signal, 1)
-
捕获 os.Interrupt 信号并把该信号发送到管道 c 中
signal.Notify(c, os.Interrupt)
-
阻塞,指导管道 c 中存在一个信号
s := <-c
-
捕获信号后做一些其它事情,比如输出
fmt.Println("Got signal:", s)
一般情况下,我们会把 3 和 4 两步骤放在一个 for select
循环中。
范例
Go 语言中处理信号的正确方式,就是使用一个 goroutine 监听来自 signal.Notify()
的信号,并在信号出现时执行一些代码
例如下面的范例,我们在 goroutine
中监听来自 signal.Notify()
的信号 os.Interrupt
,程序会在收到这个信号后做一些清理工作
package main import ( "fmt" "os" "os/signal" "syscall" "time" ) const FILE_NAME = "go-example.txt" func main() { // 设置信号处理 SetupCloseHandler() // 运行我们的程序,也就是创建一个文件并在睡眠 10 秒后继续执行 CreateFile() for { fmt.Println("- Sleeping") time.Sleep(10 * time.Second) } } // SetupCloseHandler 在一个新的 goroutine 上创建一个监听器。 // 如果接收到了一个 interrupt 信号,就会立即通知程序,做一些清理工作并退出 func SetupCloseHandler() { c := make(chan os.Signal, 2) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c fmt.Println("\r- Ctrl+C pressed in Terminal") DeleteFiles() os.Exit(0) }() } // 模仿退出时程序的清理工作。 // 因为这只是一个示例,我们并没有对错误进行任何处理 func DeleteFiles() { fmt.Println("- Run Clean Up - Delete Our Example File") _ = os.Remove(FILE_NAME) fmt.Println("- Good bye!") } // 创建一个文件,目的是模仿退出时的清理工作 func CreateFile() { fmt.Println("- Create Our Example File") file, _ := os.Create(FILE_NAME) defer file.Close() }
运行结果如下
$ go run sigint.go - Create Our Example File - Sleeping - Ctrl+C pressed in Terminal - Run Clean Up - Delete Our Example File - Good bye!