为什么/何时从另一个goroutine调用上下文取消函数会导致死锁?

为什么/何时从另一个goroutine调用上下文取消函数会导致死锁?,go,Go,我很难理解上下文取消函数的概念,在这一点上调用cancel函数会导致死锁 我有一个声明上下文的main方法,我将其cancel函数传递给两个goroutine ctx := context.Background() ctx, cancel := context.WithCancel(ctx) go runService(ctx, wg, cancel, apiChan) go api.Run(cancel, wg, apiChan, aviorDb) 我在服务函数中使用此上下文(取消上下文后停

我很难理解上下文取消函数的概念,在这一点上调用cancel函数会导致死锁

我有一个声明上下文的main方法,我将其cancel函数传递给两个goroutine

ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
go runService(ctx, wg, cancel, apiChan)
go api.Run(cancel, wg, apiChan, aviorDb)
我在服务函数中使用此上下文(取消上下文后停止的无限循环)。 我通过从另一个goroutine调用cancel函数来控制它。 runService是一个长时间运行的操作,看起来与此类似:

func runService(ctx context.Context, wg *sync.WaitGroup, cancel context.CancelFunc, apiChan chan string) {
MainLoop:
    for {    
        // this is the long running operation
        worker.ProcessJob(dataStore, client, job, resumeChan)

        select {
        case <-ctx.Done():
            _ = glg.Info("service stop signal received")
            break MainLoop
        default:
        }
        select {
        case <-resumeChan:
            continue
        default:
        }
        waitCtx, cancel := context.WithTimeout(context.Background(), time.Duration(sleepTime)*time.Minute)
        globalstate.WaitCtxCancel = cancel
        <-waitCtx.Done()

    }
    _ = dataStore.SignOutClient(client)
    apiChan <- "stop"
    wg.Done()
    cancel()
}
它在开始时由api.Run方法设置,如下所示:

func Run(cancel context.CancelFunc, wg *sync.WaitGroup, stopChan chan string, db *db.DataStore) {
    ...
    appCancel = cancel
    ...
}
api有一个停止函数,该函数调用cancel函数:

var appCancel context.CancelFunc
func requestStop(w http.ResponseWriter, r *http.Request) {
    _ = glg.Info("endpoint hit: shut down service")
    if globalstate.WaitCtxCancel != nil {
        globalstate.WaitCtxCancel()
    }
    state := globalstate.Instance()
    state.ShutdownPending = true

    appCancel()
    encoder := json.NewEncoder(w)
    encoder.SetIndent("", " ")
    _ = encoder.Encode("stop signal received")
}
当调用requestStop函数并因此取消上下文时,长时间运行的操作(worker.ProcessJob)立即停止,整个程序死锁。在执行下一行代码之前,代码跳转到
gopark
,原因是
waitReasonSemAcquire
。(这只是调试器)

上下文取消函数仅在这两个位置调用。 因此,runService goroutine似乎出于某种原因阻止了api.run goroutine获得锁

到目前为止,我的理解是cancel函数可以传递给不同的goroutine,调用它时没有同步问题

例如,当我调用WaitCtxCancel函数时,它不会导致死锁

我可以

  • 将上下文替换为1缓冲通道,并发送消息以中断循环
  • 使用我的全局状态结构和布尔值
以确定是否应运行

然而,我想知道这里发生了什么,为什么。 另外,我是否可以使用上下文来使用任何解决方案或方法? 对于像我这样的用例来说,它似乎是“正确的”东西

更新: 我最近发现

appCancel()


似乎解决了这个问题,这让我更加困惑。

带有全局变量的东西(
globalstate.WaitCtxCancel=cancel
,也许还有
appCancel
本身)在我看来可能很有活力。也许这些锁周围有锁,而这些锁本身导致了死锁?这对我来说很有意义。它并不总是发生,并且直到现在才与全局变量一起发生。为了安全和确保我的代码正常工作,我已经用通道替换了我的两个上下文。全局变量(
globalstate.WaitCtxCancel=cancel
,也许还有
appCancel
本身)的内容在我看来很有可能很有活力。也许这些锁周围有锁,而这些锁本身导致了死锁?这对我来说很有意义。它并不总是发生,并且直到现在才与全局变量一起发生。为了安全起见,并确保我的代码正常工作,我已经用通道替换了我的两个上下文。
go appCancel()