Go语言 什么是等待context cancel()完成的好做法?

x6yk4ghg  于 2023-10-14  发布在  Go
关注(0)|答案(1)|浏览(103)

给定我下面的程序,什么是一个好的做法,使方法取消(service.Start())完成(即。打印 * 完成 *)之前,通过调用cancel()函数终止?我只有一个Go例程,所以使用WaitGroup感觉不对。当我注解掉main()函数中的最后一行(time.Sleep)时,程序似乎过早地结束了,因为service.Start()没有打印 Done。如果我取消注解2秒睡眠,那么 Done 就会被打印出来。我只是想找到实现取消方法的最佳实践,以完成而不提前终止程序,但使用WaitGrouptime.Sleep()对我来说似乎是这个特定程序的一个坏做法。我使用Go例程和退出通道的原因是因为service.Start()将包含需要定期运行的逻辑,直到程序停止。

main:

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
}
7gcisfzg

7gcisfzg1#

在每个服务自己的例程中运行每个服务是一个很好的实践,在这个例程中,通过返回来确认关闭。函数或方法签名为Run-not Start-因为上下文参数适用于整个服务持续时间。
main中使用“Done”回调通道启动每个服务:

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)

退出代码约定当然是可选的,但它允许重启逻辑等。我把退出密码作为手册的一部分。

相关问题