了解 Go 中优雅的通道关闭

Understanding graceful channel close in Go

在 Go101 网站的 this article 中,我已经阅读了一些关于 stopCh 频道双重选择的技巧(在“2. 一个接收者,N 个发送者,唯一的接收者说 "please stop sending more"通过关闭一个额外的信号通道”)。

您能否描述一下它是如何工作的,我真的需要在实际应用中使用它吗?

UPD: 我没有询问频道关闭。我问过这部分代码的用法:

        // The try-receive operation is to try
        // to exit the goroutine as early as
        // possible. For this specified example,
        // it is not essential.
        select {
        case <- stopCh:
            return
        default:
        }

        // Even if stopCh is closed, the first
        // branch in the second select may be
        // still not selected for some loops if
        // the send to dataCh is also unblocked.
        // But this is acceptable for this
        // example, so the first select block
        // above can be omitted.
        select {
        case <- stopCh:
            return
        case dataCh <- rand.Intn(Max):
        }

双重选择 stopCh 的实际用例是什么?

这里的关键是了解如果可以进行多个案例,即伪随机,select 会如何表现:

  1. If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.

https://golang.org/ref/spec#Select_statements

select {
case <- stopCh:
    return
case dataCh <- rand.Intn(Max):
}

只有这第二个 select 语句,在 stopCh 关闭后,如果至少满足以下条件之一,则两种情况都可以继续进行:

  1. dataCh 已缓冲且未满
  2. 即使在 stopCh 关闭后,至少有一个 goroutine 尝试从 dataCh 接收数据

如果不明确检查 stopCh,运行时有可能(虽然不太可能)重复选择第二种情况,即使 goroutine 预计会退出。如果 goroutine 碰巧在每次迭代中发射导弹,你就会明白这可能是个问题。

如果这两个条件都可以肯定地排除,第一个select语句可以省略,因为不可能两种情况都准备好继续进行。 Go101 文章只是展示了一个保证有效的解决方案,没有做任何假设。

这种模式在现实世界的代码中并不少见,通常与上下文取消有关:

func f(ctx context.Context, ch chan T) {
    for {
        // Make sure we don't shoot after ctx has been
        // canceled, even if a target is already lined up.
        select {
        case <-ctx.Done():
            return
        default:
        }

        // Or, equivalently: if ctx.Err() != nil { return }


        select {
        case <-ctx.Done():
            return
        case t := <-ch:
            launchMissileAt(t)
        }
    }
}