聊聊在 Go 中停止程序
引言
提到如何停止 GO 程序,或许我们的第一印象就是我一个 Ctrl+C 不就解决了么。
还有什么需要聊的?哈哈哈~
回归正题,这里我们聊的是在程序中,程序自身的停止,而非收到外力强行停止程序。
在 Golang 中,有两个常用的方法 os.Exit()
和 log.Fatal()
来实现终止程序。
比如程序发版的时候,我们需要终止程序,然后运行新的程序,那么则可以监听一个 kill 信号,然后程序收到信号之后,等待协程处理完成,释放资源,最后 os.Exit(0)
os.Exit() 和 log.Fatal() 的差异
这两个函数的差异可以分别从使用场景、输出的信息、资源回收、返回值这几方面来对比
使用场景
os.Exit() 用于程序需要立即停止的情况,它通常在程序的最后阶段调用,比如在处理完所有必要的清理工作后退出程序。
log.Fatal() 用于记录一个致命错误信息,并且立即退出程序。它通常用于无法恢复的错误,如配置错误、文件系统错误等。
输出信息
os.Exit() 只负责退出程序,不会输出任何错误信息。
log.Fatal() 会先将错误信息输出到标准错误输出(stderr),然后调用 os.Exit(1) 来退出程序。
资源回收
os.Exit() 允许你在退出前执行一些清理操作,比如关闭文件句柄、释放资源等。
log.Fatal() 会在退出前执行一些默认的日志记录器的清理操作,比如同步日志文件。
返回值
os.Exit() 允许你指定一个整数作为程序的退出码,这个退出码会被操作系统接收,用于表示程序的退出状态。
log.Fatal() 默认使用退出码 1,表示程序因为严重错误而终止。
os.Exit() 和 log.Fatal() 底层实现
os.Exit()
函数在标准款文件:os/proc.go#L62
func Exit(code int) {
if code == 0 && testlog.PanicOnExit0() {
panic("unexpected call to os.Exit(0) during test")
}
//开始释放资源
runtime_beforeExit(code)
//执行系统回调,退出指令
syscall.Exit(code)
}
然后我们跟进 runtime_beforeExit
函数,进入到 runtime
包,文件见:runtime/proc.go#L305 。
os_beforeExit
里面做了几件事:
runExitHooks
执行一个在程序退出时执行的函数,如果有对个的则遵循FIFO的原则来执行执行- 这个钩子是什么呢?比如我们经常使用关闭文件的 defer fs.Close() ,就会在这里面执行
- 判断是否是正常退出,当 extiCode 是 0 的时候表示正常退出,是正常退出,则检查
raceenabled
是否存在数据竞争检测,这个全局变量是在执行go build 和go run 指定命令选项 -race, 则为 true。 - 更多关于
raceenabled
这个可以官方的文章:race-detector
// os_beforeExit is called from os.Exit(0).
//
//go:linkname os_beforeExit os.runtime_beforeExit
func os_beforeExit(exitCode int) {
runExitHooks(exitCode)
if exitCode == 0 && raceenabled {
racefini()
}
}
log.Fatal()
函数在标准款文件:log/log.go#L282
func (l *Logger) Fatal(v ...any) {
l.Output(2, fmt.Sprint(v...))
os.Exit(1)
}
函数首先会将错误信息输出到标准错误输出。这是通过 log.Output 方法实现的,它通常会将信息写入到 os.Stderr。 然后,log.Fatal() 会调用 os.Exit(1) 来退出程序。 这里的 1 是一个约定俗成的退出码,表示程序遇到了无法恢复的错误。
总结
总的来说,os.Exit() 和 log.Fatal() 都可以导致程序的退出。
但 log.Fatal() 会输出错误信息,而 os.Exit() 不会。
在实际编程中,我们可以根据具体的错误情况和需要来选择合适的函数。
如果需要记录错误信息并立即停止程序,使用 log.Fatal() 是最好不过的;
如果只是需要退出程序,并且已经通过其他方式记录了错误信息,那么使用 os.Exit() 可能更合适。