goroutine 没有看到上下文取消?

我有两个 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 / SIGTERM

log.Print("receive shutdown")

cancel()

wg.Wait()


log.Print("controller exited properly")

Suspender goroutine 成功存在(这里是代码):


package main


import (

    "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 main


import (

    "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")


    

你能帮我么 ?


茅侃侃
浏览 106回答 3
3回答

DIEA

你用错误组试过吗?它内置了上下文取消功能: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 {&nbsp; &nbsp; return eng.Watcher(ctx, wg)})wg.Go(func() error {&nbsp; &nbsp; return eng.Suspender(ctx, wg)})wg.Go(func() error {&nbsp; &nbsp; defer cancel()&nbsp; &nbsp; <-done&nbsp; &nbsp; return nil})err := wg.Wait()if err != nil {&nbsp; &nbsp; log.Print(err)}log.Print("receive shutdown")log.Print("controller exited properly")

潇湘沐

Suspenderin和 in中的代码Watcher不会通过Done()方法调用递减等待组计数器 - 无限执行背后的原因。老实说,忘记这样的小事是很正常的。这就是为什么作为 Go 中的标准一般做法,建议在一开始就使用defer和处理关键的事情(并且应该在 function/method 内部处理)。更新后的实现可能看起来像func (eng *Engine) Suspender(ctx context.Context, wg *sync.WaitGroup) error {&nbsp; &nbsp; defer wg.Done()// ------------------------------------func (eng *Engine) Watcher(ctx context.Context, wg *sync.WaitGroup) error {&nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; contextLogger := eng.logger.WithFields(log.Fields{另外,另一个建议是查看主例程,总是建议传递context by value给正在调用的任何 go-routine 或方法调用 (lambda)。这种方法使开发人员免于遇到许多不易被发现的与程序相关的错误。go func(ctx context.Context) {&nbsp; &nbsp; err := eng.Watcher(ctx, wg)&nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; cancel()&nbsp; &nbsp; }}(ctx)Edit-1:(精确解)如前所述,尝试使用 go 例程中的值传递上下文。否则,两个 go 例程都将使用单个上下文(因为您正在引用它)并且只会ctx.Done()触发一个。通过ctx作为值传递,在 Go 中创建了 2 个单独的子上下文。在使用 cancel() 关闭父级时 - 两个子级独立触发ctx.Done()。

慕少森

从表面上看,代码看起来不错。唯一能想到的就是它忙于“dostuff”。在调试器中逐步执行与计时相关的代码可能会很棘手,因此请尝试添加一些日志记录:&nbsp; case <-ticker.C:&nbsp; &nbsp; &nbsp;log.Println("doing stuff")&nbsp; &nbsp; &nbsp;//dostuff&nbsp; &nbsp; &nbsp;log.Println("done stuff")(我还假设您正在某处调用wg.Done()您的 go-routines,但如果它们丢失,那不会是您描述的问题的原因。)
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go