I have an application with multiple goroutines that are running for loops, and need a way to signal these for loops to break, and to test whether the timeout case occurred. I was looking into using a shared channel with select statements to accomplish this as follows:
// elsewhere in the code, this channel is created, and passed below
done := make(chan struct{})
time.AfterFunc(timeout, func() { close(done) })
...
go func() {
Loop:
for {
select {
case <-done:
break Loop
default:
foo()
time.Sleep(1 * time.Second)
}
}
select {
case <-done:
panic("timed out!")
default:
// ok
}
}()
Is this a valid way to accomplish this? What I'm most concerned about is that the branch of a select that is chosen could be non-deterministic, so that default may be chosen even if one of the cases is ready. Is this possible? Is there any documentation that states that a matching case is guaranteed to have preference over a default. The concern is that the for loop above could loop several times after done is closed and/or report success even though a timeout occurred.
The Go Programming Language Specification
Select statements
Execution of a "select" statement proceeds in several steps:
For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send
statements are evaluated exactly once, in source order, upon entering
the "select" statement. The result is a set of channels to receive
from or send to, and the corresponding values to send. Any side
effects in that evaluation will occur irrespective of which (if any)
communication operation is selected to proceed. Expressions on the
left-hand side of a RecvStmt with a short variable declaration or
assignment are not yet evaluated.
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.
Otherwise, if there is a default case, that case is chosen. If there
is no default case, the "select" statement blocks until at least one
of the communications can proceed.
Unless the selected case is the default case, the respective communication operation is executed.
If the selected case is a RecvStmt with a short variable declaration or an assignment, the left-hand side expressions are
evaluated and the received value (or values) are assigned.
The statement list of the selected case is executed.
"What I'm most concerned about is that the branch of a select that is
chosen could be non-deterministic, so that default may be chosen even
if one of the cases is ready. Is this possible?"
No. See step 2 of the select specification.
Related
I don't understand how the Done() channel in context.Context can work as intended. The module documentation (and source code using it) relies on this pattern:
select {
case <-ctx.Done():
return ctx.Err()
case results <- result:
}
The channel return by Done() is closed if the Context is canceled or timed out, and the Err() variable holds the reason.
I have two questions regarding this approach:
What's the behavior of select when a channel is closed? When and why is the case entered? Does the fact that there's no assignment have relevance?
According to the language reference:
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.
If the choice is random, how does that pattern guarantee that I won't be sending results down a pipeline when the Context is canceled? I would understand if the cases were evaluated in declaration order (and closed channel cases were selected).
If I'm completely off the track here, please explain this to me from a better angle.
This case:
case <-ctx.Done():
Has the communication op:
<-ctx.Done()
It's a receive from a channel. Spec: Receive operator:
A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.
So when the channel returned by ctx.Done() is closed, a receive from it can proceed immediately. Thus control flow can enter into this case.
If the other case (results <- result) can also proceed when the context is cancelled, one is chosen (pseudo-)randomly, there is no guarantee which one it will be.
If you don't want to send a value on results if the context is already cancelled, check the ctx.Done() channel before the select with another non-blocking select:
select {
case <-ctx.Done():
return ctx.Err()
default:
}
select {
case <-ctx.Done():
return ctx.Err()
case results <- result:
}
Note that you must add a default branch to the first select, else it would block until the context is cancelled. If there is a default branch and the context is not yet cancelled, default branch is chosen and so the control flow can go to the second select.
See related questions:
Force priority of go select statement
How does select work when multiple channels are involved?
incoming := make(chan int)
done := make(chan struct{})
...
close(done)
...
select {
case incoming <- order:
// logic
case <-done:
return
}
Let's say you close the done channel before the select statement:
1.The case <-done will be selected.
2.Is this because when close(done) executes, the done channel will be in a state, in which the select statement will consider the "case <-done" a match over the writing case "incoming <- order"?
The select statement blocks until one or more of the communication operations in its cases can proceed. As soon as one or more communication operations can proceed select will randomly choose one of them.
A receive operation on a closed channel can proceed immediately.
Select Statement:
Execution of a "select" statement proceeds in several steps:
For all the cases in the statement, the channel operands of
receive operations and the channel and right-hand-side expressions
of send statements are evaluated exactly once, in source order,
upon entering the "select" statement. The result is a set of
channels to receive from or send to, and the corresponding values
to send. Any side effects in that evaluation will occur
irrespective of which (if any) communication operation is selected
to proceed. Expressions on the left-hand side of a RecvStmt with a > short variable declaration or assignment are not yet evaluated.
If one or more of the communications can proceed, a single one
that can proceed is chosen via a uniform pseudo-random selection.
Otherwise, if there is a default case, that case is chosen. If
there is no default case, the "select" statement blocks until at
least one of the communications can proceed.
Unless the selected case is the default case, the respective
communication operation is executed.
If the selected case is a RecvStmt with a short variable
declaration or an assignment, the left-hand side expressions are
evaluated and the received value (or values) are assigned.
The statement list of the selected case is executed.
Receive operator:
A receive operation on a closed channel can always proceed
immediately, yielding the element type's zero value after any
previously sent values have been received.
I don't understand how the Done() channel in context.Context can work as intended. The module documentation (and source code using it) relies on this pattern:
select {
case <-ctx.Done():
return ctx.Err()
case results <- result:
}
The channel return by Done() is closed if the Context is canceled or timed out, and the Err() variable holds the reason.
I have two questions regarding this approach:
What's the behavior of select when a channel is closed? When and why is the case entered? Does the fact that there's no assignment have relevance?
According to the language reference:
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.
If the choice is random, how does that pattern guarantee that I won't be sending results down a pipeline when the Context is canceled? I would understand if the cases were evaluated in declaration order (and closed channel cases were selected).
If I'm completely off the track here, please explain this to me from a better angle.
This case:
case <-ctx.Done():
Has the communication op:
<-ctx.Done()
It's a receive from a channel. Spec: Receive operator:
A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.
So when the channel returned by ctx.Done() is closed, a receive from it can proceed immediately. Thus control flow can enter into this case.
If the other case (results <- result) can also proceed when the context is cancelled, one is chosen (pseudo-)randomly, there is no guarantee which one it will be.
If you don't want to send a value on results if the context is already cancelled, check the ctx.Done() channel before the select with another non-blocking select:
select {
case <-ctx.Done():
return ctx.Err()
default:
}
select {
case <-ctx.Done():
return ctx.Err()
case results <- result:
}
Note that you must add a default branch to the first select, else it would block until the context is cancelled. If there is a default branch and the context is not yet cancelled, default branch is chosen and so the control flow can go to the second select.
See related questions:
Force priority of go select statement
How does select work when multiple channels are involved?
I'm writing some code where I'm passing data from one channel to another one. Following some intuition and this answer I expected the following code to work (other is a sufficiently big buffered channel and out is the source channel):
for {
select {
case other <- (<-out):
log.Warn("C")
}
}
And it does! But the other cases don't trigger at all, e.g. there are no Ds in the logs for the code below:
for {
select {
case other <- (<-out):
log.Warn("C")
default:
log.Warn("D")
}
}
Using the more traditional solution, there are Ds all over the logs:
for {
select {
case msg := <-out:
other <- msg
log.Warn("C")
default:
log.Warn("D")
}
}
Obviously, I'm going with the usual solution, but I still don't know why the unusual one does not work as expected.
I suspect the answer lies somewhere in The Go Memory Model but I can't quite figure out what is exactly happening in this case.
I have put together some playgrounds where you can check out this behavior:
Unusal (there are no Ds at all)
Usual (there are a lot of Ds, you might have to try it locally to see anything other than Ds)
Thanks in advance to anyone who can shed some light on this!
When you have this:
ch := make(chan int, 10)
// ...
select {
case ch <- <-out:
fmt.Println("C")
default:
fmt.Println("D")
}
The communication op of the first case is ch <- something, where something is <-out. But the something is evaluated first, and only then is checked which communication op of the cases can proceed.
So <-out will block as long as it needs, and then ch <- something is checked if it can proceed. Since you used a big enough buffer, it can always proceed in your example, so default is never chosen.
Spec: Select statements:
Execution of a "select" statement proceeds in several steps:
For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated.
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.
Unless the selected case is the default case, the respective communication operation is executed.
If the selected case is a RecvStmt with a short variable declaration or an assignment, the left-hand side expressions are evaluated and the received value (or values) are assigned.
The statement list of the selected case is executed.
If you lower the buffer of ch, you will see occasional Ds printed in the output (try it on the Go Playground).
ch := make(chan int, 2)
When I read the docs of select section, there are some things that I really can't understand. With the docs (and the answer on StackOverflow), select will choose one case that could run (or won't block). If there are multiple case, Go will choose one of them random.
So, in my understanding, following cases should be run by random:
for {
select {
case <-time.After(time.Millisecond * 101):
fmt.Println("time out1")
case <-time.After(time.Millisecond * 100):
fmt.Println("time out2")
}
time.Sleep(time.Millisecond * 50)
}
But actually, it always print timed out2
why it only print timed out2, I think the first case also didn't block the program, If the golang could know how many time it will cost of the second case, how about replace this case to the operation which program can not know how many time will cost, like db operate, http request...
So I think, select always return that the fastest return?
time.After() returns a channel on which a value will be sent when the timeout expires.
So you have a select with 2 cases, where both receives from channels are blocking. So select blocks, waits until one of them can proceed.
Your 2 timeout values are different, so the smaller one will be ready to receive from sooner, so that case may be chosen immediately once the timeout expires.
Your loop body ends, and the next iteration begins. You create new timeout channels, so the same thing repeats.