主要内容
1. 程序为什么需要优雅退出
2. 实现办法(举例)
3. 总结
1. 程序为什么需要优雅退出
原因很简单,我们都不希望自己的程序被异常关闭或者ctrl+c
给直接干掉,或许我们这回正在写数据库,或许正在完成一个复杂的计算流程;我们希望程序能在完成手头的工作之后才关闭,就好比编辑器退出是自动保存一样,防止之前的工作白费,更糟糕的是,导致异常或者不一致的数据;
2.实现办法(举例)
其实实现办法很简单,golang提供了现成的包来帮助我们解决这个问题:
signal.Notify(c chan<- os.Signal, sig ...os.Signal)
第一个参数:一个接受信号的通道
其他参数:为需要捕获的系统信号的数组
当相关系统信号被触发时,c中将会有数据,可通过c来阻塞程序,来实现在接到系统信号后自定义处理逻辑;
关于所有系统信号的介绍可参考:https://godoc.org/os/signal
其次,就是context的使用,用于在不同协程之间完成通信;要实现优雅退出,需要通过context将程序结束的消息通知到各个正在处理的协程,让他们做好退出准备(只处理手头的任务);
ctx, cancel := context.WithCancel(context.Background())
context.WithCancel返回一个新的context和与之对应的cancel函数,调用cancel函数,将会降Done的信号通知到所有正在使用ctx的协程;
完整示例
package main
import (
"syscall"
"os"
"os/signal"
"time"
"context"
"log"
)
func main() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGHUP)
ctx, cancel := context.WithCancel(context.Background())
line := make(chan int, 1)
exit := make(chan bool, 1)
go Producer(line)
go WorkFunc(ctx, line, exit)
<- sigs
cancel()
<- exit
}
// 工作协程
func WorkFunc(ctx context.Context, line chan int, exit chan bool) {
for {
select {
case n := <- line:
log.Println("work start:", n)
time.Sleep(1 * time.Second)
log.Println("work done:", n)
case <- ctx.Done():
log.Println("exit")
goto EXIT
}
}
EXIT:
exit <- true
}
// 生产协程
func Producer(line chan int) {
for i:=0; i < 10; i++ {
line <- i
time.Sleep(time.Second)
}
}
3. 总结
程序的优雅退出时相当重要的,对于维护数据的完整性至关重要,也是一种很好的编码习惯;上面的示例提供了一种实现的方式,但是不同的运用场景可能需要更加细致的考虑;同时,也没办法处理kill -9
这样暴力的关闭进程的场景;