在 Go 中处理 long-运行 操作时等待上下文 Done 通道取消

Wait for context Done channel for cancellation while working on long-run operation in Go

在这种情况下:

  1. 第一个 go 例程正在处理一个长运行 操作并阻塞直到它完成
  2. 第二个go例程可能随时取消整个任务
  3. 当整个任务被取消时,第一个go例程应该立即退出操作并return。

这是我的解决方案。它可以工作,但感觉不优雅或 Go 风格。

你能纠正我或告诉我更好的解决方案吗?

    var (
        workTimeCost  = 6 * time.Second
        cancelTimeout = 5 * time.Second
    )

    ctx, cancel := context.WithCancel(context.Background())

    var (
        data   int
        readCh = make(chan struct{})
    )
    go func() {
        log.Println("blocked to read data")
        // fake long i/o operations
        time.Sleep(workTimeCost)
        data = 10
        log.Println("done read data")

        readCh <- struct{}{}
    }()

    // fake cancel is called from the other routine (it's actually not caused by timeout)
    time.AfterFunc(cancelTimeout, cancel)

    select {
    case <-ctx.Done():
        log.Println("cancelled")
        return
    case <-readCh:
        break
    }

    log.Println("got final data", data)

Close readCh 表示长 运行 goroutine 完成。与发送值相比,关闭通道有两个好处:

  • 用 defer 方便地调用 close
  • close 在上下文被取消的情况下不会阻塞。如果上下文在 goroutine 完成之前被取消,则问题中的代码会泄漏 goroutine。

这是更新后的代码:

var (
    workTimeCost  = 6 * time.Second
    cancelTimeout = 5 * time.Second
)

ctx, cancel := context.WithCancel(context.Background())

var (
    data   int
    readCh = make(chan struct{})
)
go func() {
    defer close(readCh)
    log.Println("blocked to read data")
    // fake long i/o operations
    time.Sleep(workTimeCost)
    data = 10
    log.Println("done read data")
}()

// fake cancel is called from the other routine (it's actually not caused by timeout)
time.AfterFunc(cancelTimeout, cancel)

select {
case <-ctx.Done():
    log.Println("cancelled")
    return
case <-readCh:
    break
}

log.Println("got final data", data)

如果不需要区分long-运行 goroutine的完成和取消,从goroutine调用cancel函数。

var (
    workTimeCost  = 6 * time.Second
    cancelTimeout = 5 * time.Second
)

ctx, cancel := context.WithCancel(context.Background())

var data int

go func() {
    defer cancel()
    log.Println("blocked to read data")
    // fake long i/o operations
    time.Sleep(workTimeCost)
    data = 10
    log.Println("done read data")
}()

// fake cancel is called from the other routine (it's actually not caused by timeout)
time.AfterFunc(cancelTimeout, cancel)

<-ctx.Done()

log.Println("got final data", data)