背景:昨晚实现一个服务注册到consul中,测试过程中使用crl+c使服务退出,consul并不会立刻将该服务移除,而是等待一定时间后(可以自行设置)才会将其移除,于是就想使用signal.Notify实现优雅退出功能使consoul立即移除,使用过程出现一个提示:
the channel used with signal.Notify should be buffered
以前实现graceful shutdown功能并没有注意这个问题,上网查资料发现存在一定的风险。现在举例说明:
- 使用
unbuffered channel
func main(){
......
go func() {
err = server.Serve(lis)
if err != nil && err != http.ErrServerClosed {
zap.S().Fatalf("failed to serve: %v", err)
}
}()
//接收终止信号
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
if err = client.Agent().ServiceDeregister(serviceID); err != nil {
zap.S().Info("注销失败")
}
zap.S().Info("注销成功")
}
执行上述代码,然后按下ctrl + c,正常情况下你会看到注销成功,如果在接收channel前面处理一些复杂的工作呢?我们使用简单的demo测试一下:
package main
func main() {
quit := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
time.Sleep(6 * time.Second)
// Block until a signal is received.
s :=<-quit
fmt.Println("signal为:", s)
}
执行上述代码,然后按下ctrl + c,你会发现6秒内,怎么按ctrl + c成会都会退出,直到6秒后,整个程序也不会终止,需要再按ctrl + c才会终止,我们期望是,在前6秒內,按下任何一次ctrl + c,6秒要能正常接收到第一次传来的信号,看看源码究竟是什么原因?
-
形成的原因
点击
signal.Notify打开singal.go文档,找到process函数,可以看到以下代码:for c, h := range handlers.m { if h.want(n) { // send but do not block for it select { case c <- sig: default: } } }从上面代码可知,如果使用
unbuffered channel,那么在6秒內接收到的任何信号,都会到 default 条件内,所以造成 Channel 不会收到任何值,这就是为什么6秒內的任何动作,在6秒后都完全收不到。这也是出现蓝色提示(the channel used with signal.Notify should be buffered)的原因。为了避免这件发生,所以通常我们会将 signal channel 的缓冲区设置为 1,来避免需要中断程式时,确保主程式可以收到一个signal信号。