Goroutine看不到上下文取消?

发布于 2025-02-06 10:09:44 字数 2262 浏览 1 评论 0原文

我同时有两个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 {
        cancel()
    }
}()

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

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

    ticker := time.NewTicker(time.Second * 30)
    for {
        select {
        case <-ctx.Done():
            contextLogger.Infof("goroutine watcher canceled by context")
            log.Printf("toto")
            return nil
        case <-ticker.C:
            
                //dostuff
            }
        }
    }
}

您能帮我吗?

谢谢 :)

I have two goroutines running at the same time.

At some point, I want my program to exit gracefully so I use the cancel() func to notify my goroutines that they need to be stopped, but only one of the two receive the message.

here is my main (simplified):

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 {
        cancel()
    }
}()

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

The Suspender goroutine exist successfully (here is the code):

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

}

and here is the func that is not receiving the context cancellation:

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

    ticker := time.NewTicker(time.Second * 30)
    for {
        select {
        case <-ctx.Done():
            contextLogger.Infof("goroutine watcher canceled by context")
            log.Printf("toto")
            return nil
        case <-ticker.C:
            
                //dostuff
            }
        }
    }
}

Can you please help me ?

Thanks :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

耀眼的星火 2025-02-13 10:09:44

您是否使用Errgroup尝试过?它具有上下文取消的烤制:

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

Did you try it with an errgroup? It has context cancellation baked in:

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")
苏佲洛 2025-02-13 10:09:44

从表面上看,代码看起来不错。我唯一能想到的是它忙于“ dostuff”。在调试器中逐步浏览与计时相关的代码可能很棘手

  case <-ticker.C:
     log.Println("doing stuff")
     //dostuff
     log.Println("done stuff")

:不是您描述的问题的原因。)

On the surface the code looks good. The only thing I can think is that it's busy in "dostuff". It can be tricky to step through timing related code in the debugger so try adding some logging:

  case <-ticker.C:
     log.Println("doing stuff")
     //dostuff
     log.Println("done stuff")

(I also assume you are calling wg.Done() in your go-routines somewhere though if they are missing that would not be the cause of the problem you describe.)

巷雨优美回忆 2025-02-13 10:09:44

suspenderWatcher中的代码不会通过done()方法调用 - 无限执行背后的原因。

老实说,忘记这么小的事情是很正常的。这就是为什么作为GO中的标准通用实践,建议使用defer并处理至关重要的事情(应在功能/方法内处理)。

更新的实现可能也可能看起来像是

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{

另一个建议,查看主要例程,始终建议通过值将上下文传递给被调用的任何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作为值2在GO中创建单独的子上下文。并且在用cancel()结束父母时 - 两个孩子都独立发射ctx.done()

The code in Suspender and in Watcher doesn't decrement the waitgroup counter through the Done() method call - the reason behind the infinite execution.

And to be honest it's quite normal to forget such small things. That's why as a standard general practice in Go, it is suggested to use defer and handle things that are critical (and should be handled inside the function/method ) at the very beginning.

The updated implementation might look like

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{

Also, another suggestion, looking at the main routine, it is always suggested to pass context by value to any go-routine or method calls (lambda) that are being invoked.
This approach saves developers from a lot of program-related bugs that can't be noticed very easily.

go func(ctx context.Context) {
    err := eng.Watcher(ctx, wg)
    if err != nil {
        cancel()
    }
}(ctx)

Edit-1: (the exact solution)

Try passing the context using the value in the go routines as I mentioned earlier. Otherwise, both of the go routine will use a single context (because you are referencing it) and only one ctx.Done() will be fired.
By passing ctx as a value 2 separate child contexts are created in Go. And while closing parent with cancel() - both children independently fires ctx.Done().

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文