优雅的结束golang程序
实现方式
使用sync.WaitGroup
配合context.WithCancel
即可优雅的关掉
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
signChan := make(chan os.Signal)
signal.Notify(signChan, syscall.SIGINT) // ctrl+c
signal.Notify(signChan, syscall.SIGTERM) // kill
go func() {
force := false
for {
oscall := <-signChan
if force {
os.Exit(1)
}
log.Infof("receive system call: %v", oscall)
cancel()
force = true
log.Info("press ctrl+c again to force exit")
}
}()
tasks := make([]func(), 0)
tasks = append(tasks, func() {
someCtx := ....
someChan := ....
for {
select {
case a := <- someChan:
handle(a)
case <-someCtx.Done():
return
case <-ctx.Done():
return
}
}
defer wg.Done()
})
tasks = append(tasks, func() {
for {
// 模拟执行了其他耗时操作
time.Sleep(10 * time.Second)
if ctx.Err() != nil {
// cancel() called
return
}
}
})
// ... add more task
wg.Add(len(tasks))
for _, f := range tasks {
go f()
}
log.Info("running")
wg.Wait()
log.Info("shutdown")
代码里我们首先监听 SIGINT 和 SIGTERM, 当程序收到系统信号后调用cancel()
通知子task该停止工作了。这里我们
增加了一个force
变量,保证可以通过两次ctrl+c命令强行终止程序。
其中 SIGINT 响应ctrl+c命令。SIGTERM 响应系统kill命令(docker stop就是发送此signal)
注意事项
如何正常响应docker stop
有时候会发现运行在docker中的go程序无法正确响应系统信号, 查看docker文档中提到了,docker stop会执行如下操作
The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL.
注意这里提到了 main process,对于docker容器来说就是向pid为1的进程。很多时候我们的container command命令中运行的是一个脚本文件,类似
sh /app/start.sh,文件内容类似如下
echo "start app"
/app/web
这样的脚本执行结果就是 main process 是sh /app/start.sh,而不是我们的/app/web程序,此时要做的就是使用exec命令运行我们的程序
echo "start app"
exec /app/web
这样我们的/app/web就是pid为1的进程了,也就可以正常的收到SIGTERM系统信号