以下是笔者在近几年 Go 开发过程中,总结出来的一个相对来说很完善的 Go 服务 main() 函数编写范式
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
// 使用 defer 处理 err,如果发现 err 不为 nil,则使用 exit_code 1 退出
var err error
defer func(err *error) {
if *err != nil {
log.Println("exited with error:", (*err).Error())
os.Exit(1)
} else {
log.Println("exited")
}
}(&err)
// 此后任何报错,均可以赋值 err 后直接返回
// 前面的 defer 可以负责完成错误打印和设置退出码
if err = doSomeSetup(); err != nil {
return
}
// 以 http.Server 为例
s := &http.Server{
Addr: ":8080",
}
// 错误管道
chErr := make(chan error, 1)
// 信号管道,并捕获 SIGINT 和 SIGTERM
chSig := make(chan os.Signal, 1)
signal.Notify(chSig, syscall.SIGINT, syscall.SIGTERM)
// 在一个 goroutine 中执行 s.ListenAndServe(),并把错误传递给 chErr
go func() {
chErr <- s.ListenAndServe()
}()
select {
// 如果 chErr 管道中拿到了 err,说明服务没有成功启动监听,直接 return 报错退出
case err = <-chErr:
return
// 如果 chSig 管道中拿到了 sig, 说明收到了 SIGINT 或者 SIGTERM,等待若干秒后,开始进行优雅停机
case sig := <-chSig:
log.Println("signal caught:", sig.String())
time.Sleep(time.Second * 3)
}
// 优雅停机,同时将错误返回
err = s.Shutdown(context.Background())
}