首页 > 解决方案 > 如何确保上下文侦听器例程在主退出之前完成?

问题描述

以下是我遇到的场景的简化示例。去这里的游乐场。

主要方法是定期调用该process函数。进程函数获得一个锁,它必须在它返回之前或在应用程序由于中断而关闭之前释放。

通过取消传递给的上下文来处理中断process。在我的示例中,我只是取消了上下文。

如何确保在取消上下文时解锁逻辑执行完成?

ATM,在我的测试中,逻辑没有被执行完成。该例程已启动,但在程序退出之前似乎并未完成。

就好像 main 方法正在退出,中途杀死例程。那可能吗?如何确保例程始终在程序退出之前完成?谢谢!

package main

import (
    "fmt"
    "context"
    "time"
    "sync"
)

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    
    var wg sync.WaitGroup
    wg.Add(1)
    go func(){
        fmt.Println("Started periodic process")
        defer wg.Done()
        ticker := time.NewTicker(20 * time.Millisecond)
        for {
            select {
                case <-ticker.C:
                    process(ctx)
                case <-ctx.Done():
                    ticker.Stop()                   
    
            }
        }
        fmt.Println("Finished periodic process")
    }()
    
    time.Sleep(100 * time.Millisecond)  
    
    // cancel context
    cancel()
    
    wg.Wait()
}

func process(ctx context.Context) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    
    go func() {
        <-ctx.Done()
        
        // unlock resources
        fmt.Println("Unlocking resources")

        time.Sleep(20 * time.Millisecond)

        fmt.Println("Unlocked resources")       
    }()
    
    // do some work
    fmt.Println("Started process")
    
    // acquire lock, in actual process
    // acquiring lock will fail unless
    // it was released be previous process
    // run
    fmt.Println("Acquired lock")
    
    time.Sleep(10 * time.Millisecond)
    
    fmt.Println("Finished process")

}



标签: go

解决方案


就目前而言,您的程序将永远不会退出。这是因为forgoroutine 中开始的循环main永远不会退出(当你停止代码ctx.Done()但不退出循环时)。

第二个问题是,在process函数退出时(通过defer cancel())取消了 goroutine,但是由于延迟,goroutine 将继续运行一段时间。这里的解决方案取决于您是否需要异步进行“解锁”(我认为这并不重要)。

以下(游乐场)解决了这两个问题并确保process在资源空闲之前不会返回;如果您需要并行释放这些,那么一种选择是将 传递WaitGroup给函数)

package main

import (
    "fmt"
    "context"
    "time"
    "sync"
)

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    
    var wg sync.WaitGroup
    wg.Add(1)
    go func(){
            fmt.Println("Started periodic process")
        defer wg.Done()
        ticker := time.NewTicker(20 * time.Millisecond)
        for {
            select {
                case <-ticker.C:
                    process(ctx)
                case <-ctx.Done():
                    ticker.Stop()                   
                    fmt.Println("Finished periodic process")
                    return
            }
        }       
    }() 
    time.Sleep(100 * time.Millisecond)  
    cancel()    
    wg.Wait()
}

func process(ctx context.Context) {
    ctx, cancel := context.WithCancel(ctx)
    done := make(chan struct{}) // could also use a waitgroup
    
    go func() {
        <-ctx.Done()
        fmt.Println("Unlocking resources")
        time.Sleep(10 * time.Millisecond)
        fmt.Println("Unlocked resources")   
        close(done) 
    }()
    

    // do some work
    fmt.Println("Started process")  
    time.Sleep(10 * time.Millisecond)   
    fmt.Println("Finished process")
    
    cancel()
    <-done // Wait for resources to be unlocked
}

推荐阅读