了解 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 会如何表现:
- 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
关闭后,如果至少满足以下条件之一,则两种情况都可以继续进行:
- dataCh 已缓冲且未满
- 即使在
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)
}
}
}
在 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 会如何表现:
- 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
关闭后,如果至少满足以下条件之一,则两种情况都可以继续进行:
- dataCh 已缓冲且未满
- 即使在
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)
}
}
}