在看示例之前有必要先看看Go程序中信号的默认行为,
go中信号的默认行为如下:
SIGHUP、SIGINT或SIGTERM信号会导致程序退出。SIGQUIT、SIGILL、SIGTRAP、SIGABRT、SIGSTKFLT、SIGEMT或SIGSYS信号会导致程序退出并进行堆栈转储。SIGTSTP、SIGTTIN或SIGTTOU信号获取系统默认行为(这些信号由shell用于作业控制)。SIGPROF信号由Go运行时直接处理以实现运行时。CPU配置文件。将捕捉到其他信号,但不会采取任何行动。
如果Go程序启动时忽略了SIGHUP或SIGINT(信号处理程序设置为SIG_IGN),它们将保持被忽略状态。
如果Go程序是用一个非空信号掩码启动的,那么这通常会得到遵守。然而,有些信号是明确取消阻塞的:同步信号SIGILL、SIGTRAP、SIGSTKFLT、SIGCHLD、SIGPROF,以及在Linux上的信号32(SIGCANCEL)和33(SIGSETXID)(SIGCANCEL和SIGSETXID由glibc内部使用)。操作系统启动的子进程。Exec或os/Exec将继承修改后的信号掩码。
信号通知使用示例:
Go
package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
)
// 中断信号演示,
// signal.Notify(c, os.Interrupt) 这里的第二个参数即为要中转的信号,如果没有这个参数,所有的信号都会被中转到c
func demoSigInterrupt() {
c := make(chan os.Signal, 1) //创建一个用来接收信号的管道 c
signal.Notify(c, os.Interrupt) //将包中接收到的中断信号(os.Interrupt)转送给管道c; 当程序被ctrl+c中断时中断信号就会被转送给管c
s := <-c // 从管道中读取信息并赋值给s;
fmt.Println("Got signal:", s) // Got signal: interrupt
}
// 中转所有信号
func demoSigAll() {
c := make(chan os.Signal, 1)
signal.Notify(c) // 不传第二个参数,将所有的OS信号都转送到c
s := <-c
fmt.Println("Got signal: ", s)
}
var neverRead = make(chan struct{})
// This example passes a context with a signal to tell a blocking function that
// it should abandon its work after a signal is received.
func demoNotifyContext() {
// NotifyContext返回一份父上下文的拷贝, 当收到os.Interrupt信号或者stop函数被执行时, 这时这个上下文的通道就会被关闭
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
// 获取当前进程自己
p, err := os.FindProcess(os.Getpid())
if err != nil {
log.Fatal(err)
}
// 在unix类的系统中,ctrl+c将发送一个SIGINT信号到执行中的程序
// 注意下面这个代码仅在unix系统中生效, 可在文件顶部添加 //go:build unix 来指示仅unix系统才会被编译
// 这里模拟先自己发送一个SIGINT中断信号
if err := p.Signal(os.Interrupt); err != nil {
log.Fatal(err)
}
// 处理多个管道模拟
select {
case <-neverRead:
fmt.Println("这个不会被执行,因为它是空的,仅作为多管道模拟")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context canceled"
stop() // stop receiving signal notifications as soon as possible.
}
}
func main() {
// demoSigInterrupt()
// demoSigAll()
demoNotifyContext()
fmt.Println("main ended")
}