为了账号安全,请及时绑定邮箱和手机立即绑定

goroutine 没有看到上下文取消?

goroutine 没有看到上下文取消?

Go
茅侃侃 2022-12-19 21:06:55
我有两个 goroutines 同时运行。在某些时候,我希望我的程序正常退出,所以我使用cancel()func 通知我的 goroutines 它们需要停止,但只有两个中的一个收到消息。这是我的主要(简化):ctx := context.Background()ctx, cancel := context.WithCancel(ctx)done := make(chan os.Signal, 1)signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)wg := &sync.WaitGroup{}wg.Add(2)go func() {    err := eng.Watcher(ctx, wg)    if err != nil {        ancel()    }}()go func() {    err := eng.Suspender(ctx, wg)    if err != nil {        cancel()    }}()<-done // wait for SIGINT / SIGTERMlog.Print("receive shutdown")cancel()wg.Wait()log.Print("controller exited properly")Suspender goroutine 成功存在(这里是代码):package mainimport (    "context"    "sync"    "time"    log "github.com/sirupsen/logrus"    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"    "k8s.io/client-go/util/retry")func (eng *Engine) Suspender(ctx context.Context, wg *sync.WaitGroup) error {    contextLogger := eng.logger.WithFields(log.Fields{        "go-routine": "Suspender",    })    contextLogger.Info("starting Suspender goroutine")    now := time.Now().In(eng.loc)    for {        select {        case n := <-eng.Wl:            //dostuff        case <-ctx.Done():            // The context is over, stop processing results            contextLogger.Infof("goroutine Suspender canceled by context")            return nil        }    }}这是未接收上下文取消的函数:package mainimport (    "context"    "sync"    "time"    log "github.com/sirupsen/logrus")func (eng *Engine) Watcher(ctx context.Context, wg *sync.WaitGroup) error {    contextLogger := eng.logger.WithFields(log.Fields{        "go-routine":      "Watcher",        "uptime-schedule": eng.upTimeSchedule,    })    contextLogger.Info("starting Watcher goroutine")    你能帮我么 ?
查看完整描述

3 回答

?
DIEA

TA贡献1820条经验 获得超2个赞

你用错误组试过吗?它内置了上下文取消功能:


ctx := context.Background()

ctx, cancel := context.WithCancel(ctx)

defer cancel()


done := make(chan os.Signal, 1)

signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)


// "golang.org/x/sync/errgroup"

wg, ctx := errgroup.WithContext(ctx)


wg.Go(func() error {

    return eng.Watcher(ctx, wg)

})


wg.Go(func() error {

    return eng.Suspender(ctx, wg)

})


wg.Go(func() error {

    defer cancel()

    <-done

    return nil

})


err := wg.Wait()

if err != nil {

    log.Print(err)

}


log.Print("receive shutdown")

log.Print("controller exited properly")


查看完整回答
反对 回复 2022-12-19
?
潇湘沐

TA贡献1816条经验 获得超6个赞

Suspenderin和 in中的代码Watcher不会通过Done()方法调用递减等待组计数器 - 无限执行背后的原因。


老实说,忘记这样的小事是很正常的。这就是为什么作为 Go 中的标准一般做法,建议在一开始就使用defer和处理关键的事情(并且应该在 function/method 内部处理)。


更新后的实现可能看起来像


func (eng *Engine) Suspender(ctx context.Context, wg *sync.WaitGroup) error {

    defer wg.Done()


// ------------------------------------


func (eng *Engine) Watcher(ctx context.Context, wg *sync.WaitGroup) error {

    defer wg.Done()

    contextLogger := eng.logger.WithFields(log.Fields{


另外,另一个建议是查看主例程,总是建议传递context by value给正在调用的任何 go-routine 或方法调用 (lambda)。这种方法使开发人员免于遇到许多不易被发现的与程序相关的错误。


go func(ctx context.Context) {

    err := eng.Watcher(ctx, wg)

    if err != nil {

        cancel()

    }

}(ctx)

Edit-1:(精确解)


如前所述,尝试使用 go 例程中的值传递上下文。否则,两个 go 例程都将使用单个上下文(因为您正在引用它)并且只会ctx.Done()触发一个。通过ctx作为值传递,在 Go 中创建了 2 个单独的子上下文。在使用 cancel() 关闭父级时 - 两个子级独立触发ctx.Done()。


查看完整回答
反对 回复 2022-12-19
?
慕少森

TA贡献2019条经验 获得超9个赞

从表面上看,代码看起来不错。唯一能想到的就是它忙于“dostuff”。在调试器中逐步执行与计时相关的代码可能会很棘手,因此请尝试添加一些日志记录:


  case <-ticker.C:

     log.Println("doing stuff")

     //dostuff

     log.Println("done stuff")

(我还假设您正在某处调用wg.Done()您的 go-routines,但如果它们丢失,那不会是您描述的问题的原因。)


查看完整回答
反对 回复 2022-12-19
  • 3 回答
  • 0 关注
  • 104 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信