In this article from Go101 site I have read some trick with double selection for stopCh
channel (in section "2. One receiver, N senders, the only receiver says "please stop sending more" by closing an additional signal channel").
Could you please describe how it works and do I really need to use it in real-world applications?
UPD: I don't asked about channel close. I have asked about usage of this part of the code:
// 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):
}
What is the real use case for double selection of stopCh
?
The key here is to understand how select behaves if multiple cases can proceed, namely pseudo-randomly:
- 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):
}
With only this second select statement, after stopCh
has been closed, it is possible that both cases can proceed if at least one of the following is true:
dataCh
even after stopCh
has been closedWithout checking stopCh
explicitely it is possible (albeit unlikely), that the runtime repeatedly chooses the second case even though the goroutine is expected to exit. If the goroutine happens to launch a missile in every iteration you can see how this may be a problem.
If both of these condition can be positively ruled out, the first select statement can be omitted because it is impossible that both cases are ready to proceed. The Go101 article simply shows a solution that is guaranteed to work, without making any assumptions.
This pattern is not uncommon in real-world code, and typically related to context cancellation:
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)
}
}
}