Go简单的结束程序
前请概要
当你写的Go程序在退出时做一些操作就需要捕捉信号后进行业务处理再关闭程序
但是当程序主线不止一条时,逻辑会些许复杂。一般捕捉信号的函数只有一个,但是退出的地方却多了起来,退出方案必须具有拓展性与简单性才能让自己不再纠结于此。
一般资料都时在讲捕捉信号与退出,而例子往往只考虑单个主线退出,当你写个命令行启动,根据子命令执行不同主线时在方便的扩展性上迷了方向。
举个栗子
单条主线
func main() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill)
// TODO: do something
s := <-c
fmt.Println("Got signal:", s)
}
全在main里干了,耦合性太强,业务需在main中按逻辑调用,使用读写无缓冲区通道会阻塞的特点
多条主线
我有个命令行程序,一个子命令启动server,一个子命令启动client,两个命令启动的程序都需要清理退出。
- 暂且把信号通道作为全局变量放在global.go
var ExitChan =make(chan string)
var SigChan = make(chan os.Signal)
var Wt=sync.WaitGroup{}
- main.go只处理信号监听与cmd解析主入口,然后阻塞等待协程。这里把信号转换到字符串通道是为了兼容其他错误退出的需求
func exitListen() {
signal.Notify(utils.SigChan, syscall.SIGINT, syscall.SIGTERM)
utils.ExitChan <- fmt.Sprintf("%v", <-utils.SigChan)
}
func main() {
go exitListen()
go cmd.Execute()
utils.Wt.Add(1)
utils.Wt.Wait()
}
- server.go 模板方式初见端倪,只需在业务之上做一个信号退出处理,然后将主协程阻塞Done一下,整个程序就结束了,客户端照葫芦画瓢,不需要侵入业务进行管理协程。
var serverCmd = &cobra.Command{
Use: "server",
Aliases: []string{"s","service"},
Short: "启动服务",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
go func() {
logrus.Warnf("服务中止,退出!%v", <-utils.ExitChan)
communication.Offline()
utils.Wt.Done()
}()
communication.UdpListen()
communication.Online()
server:=communication.NewMessageServer()
server.Listen()
},
}
结语
此方案适用于不需要复杂精细控制子协程的场合,相较于只阻塞监听信号,然后处理退出的思想来说多了多主线的兼容性,且简单的做到多信号抢占,只要一个触发可以退出所有。