鉴于我有下面的程序,在通过调用
service.Start()
函数终止之前使方法取消 (cancel()
) 完成(即打印 Done)的好做法是什么?我只有一个 Go 例程,因此使用
WaitGroup
感觉不太好。当我注释掉 time.Sleep
函数中的最后一行 (main()
) 时,程序似乎提前完成,因为 service.Start()
不打印 Done。如果我取消注释 2 秒睡眠,则会打印Done。我只是想找到实现取消方法的最佳实践,以在不提前终止程序的情况下完成,但在我看来,使用 WaitGroup
或 time.Sleep()
似乎对于这个特定程序来说是一种不好的做法。我使用 Go 例程和退出通道的原因是因为 service.Start()
将包含需要定期运行直到程序停止的逻辑。
主要:
func main() {
log.Infoln("starting service..")
ctx := context.Background()
srv := service.Service{}
exitCh := make(chan os.Signal, 1)
signal.Notify(exitCh, syscall.SIGTERM, // terminate: stopped by `kill -9 PID`
syscall.SIGINT, // interrupt: stopped by Ctrl + C
)
ctxCancel, cancel := context.WithCancel(ctx)
go run(ctxCancel, srv, exitCh)
<-exitCh // blocking until receive exit signal
cancel()
time.Sleep(time.Second * 2)
}
func run(ctx context.Context, srv service.Service, exitCh chan<- os.Signal) {
defer func() {
exitCh <- syscall.SIGTERM // send terminate signal when application stop naturally
}()
err := srv.Start(ctx)
if err != nil {
log.Warningf("canceling all operations: %s", err)
}
}
服务:
type Service struct {}
func (s *Service) Start(ctx context.Context) error {
for i := 0; i < 5; i++ {
select {
case <-ctx.Done():
log.Println("Done")
return ctx.Err()
default:
log.Printf("i value: %d", i)
time.Sleep(3 * time.Second)
}
}
return nil
}
最好的做法是在自己的例程中运行每个服务,其中通过返回确认关闭。函数或方法签名是 Run(而不是 Start),因为上下文参数适用于整个服务持续时间。
使用
main
中的“完成”回调通道启动每个服务:
fooDone := make(chan struct{})
go func() {
defer close(fooDone)
err := foo.Run(ctx)
if !errors.Is(ctx.Canceled) {
log.Print("service foo terminated: ", err)
ctxCancel() // shutdown
}
}()
SIGTERM 不是评论所述的
kill -9
。 SIGKIL 是。 SIGKILL 无法等待几秒钟才能完成,但 SIGINT 和 SIGTERM 可能应该等待。
继续
main
退出处理:
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGKILL, syscall.SIGTERM)
var sig os.Signal
select {
case <-ctx.Done():
case sig := <-sigs:
log.Print("got signal ", sig)
ctxCancel()
}
// trigger any termination beyond Context
bar.Shutdown()
switch (sig) {
case syscall.SIGINT, syscall.SIGTERM:
// await termination within reasonable limit
timeout := timer.After(3*time.Second)
select {
case <-fooDone:
case <-timeout:
log.Print("foo termination timed out")
}
// repeat for each service …
}
if (n, ok := sig.(syscall.Signal); ok) {
os.exit(128 + int(n)) // Unix convention
}
os.exit(255)
退出代码约定当然是可选的,但它允许重新启动逻辑等。我将记录退出代码作为手册的一部分。