“Select” statement inside of goroutine with for loop
“Select” statement inside of goroutine with for loop
Could somebody please explain, why if goroutine has endless for
loop and select
inside of the loop, a code in the loop is run only one time?
for
select
package main
import (
"time"
)
func f1(quit chan bool)
go func()
for
println("f1 is working...")
time.Sleep(1 * time.Second)
select
case <-quit:
println("stopping f1")
break
()
func main()
quit := make(chan bool)
f1(quit)
time.Sleep(4 * time.Second)
Output:
f1 is working...
Program exited.
But if "select" is commented out:
package main
import (
"time"
)
func f1(quit chan bool)
go func()
for
println("f1 is working...")
time.Sleep(1 * time.Second)
//select
//case <-quit:
//println("stopping f1")
//break
//
()
func main()
quit := make(chan bool)
f1(quit)
time.Sleep(4 * time.Second)
Output:
f1 is working...
f1 is working...
f1 is working...
f1 is working...
f1 is working...
https://play.golang.org/p/MxKy2XqQlt8
for
@Ullaakut The
break
applies, in this case, to the select
statement, not the for
loop, because it's scoped to a case
statement inside the select
. It's also superfluous, as break is implied when the end of a case is reached (an explicit break is not required in Go like it is in C++).– Kaedys
Sep 17 '18 at 17:54
break
select
for
case
select
1 Answer
1
A select
statement without a default
case is blocking until a read or write in at least one of the case
statements can be executed. Accordingly, your select
will block until a read from the quit
channel is possible (either of a value or the zero value if the channel is closed). The language spec provides a concrete description of this behavior, specifically:
select
default
case
select
quit
If one or more of the communications [expressed in the case
statements] 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.
case
Other code issues
break
select
However, even if you did close the quit
channel to signal shutdown of your program, your implementation would likely not have the desired effect. A (unlabelled) call to break
will terminate execution of the inner-most for
, select
or switch
statement within the function. In this case, the select
statement will break and the for
loop will run through again. If quit
was closed, it will run forever until something else stops the program, otherwise it will block again in select
(Playground example)
quit
break
for
select
switch
select
for
quit
select
quit
As noted in the comments, the call to time.Sleep
blocks for a second on each iteration of the loop, so any attempt to stop the program by closing quit
will be delayed for up to approximately a second before the goroutine checks quit
and escapes. It is unlikely this sleep period must complete in its entirety before the program stops.
time.Sleep
quit
quit
More idiomatic Go would block in the select
statement on receiving from two channels:
select
quit
time.After
Timer
Resolution
A resolution to fix your immediate issue with minimal changes to your code would be:
quit
select
quit
for
break
return
f1
Depending on your circumstances, you may find it more idiomatic Go to use a context.Context
to signal termination, and to use a sync.WaitGroup
to wait for the goroutine to finish before returning from main
.
context.Context
sync.WaitGroup
main
package main
import (
"fmt"
"time"
)
func f1(quit chan bool)
go func()
for
println("f1 is working...")
time.Sleep(1 * time.Second)
select
case <-quit:
fmt.Println("stopping")
return
default:
()
func main()
quit := make(chan bool)
f1(quit)
time.Sleep(4 * time.Second)
close(quit)
time.Sleep(4 * time.Second)
Working example
(Note: I have added an additional time.Sleep
call in your main
method to avoid this returning immediately after the call to close
and terminating the program.)
time.Sleep
main
close
To fix the additional issue regarding the blocking sleep preventing immediate quit, move the sleep to a timer in the select
block. Modifying your for
loop as per this playground example from the comments does exactly this:
select
for
for
println("f1 is working...")
select
case <-quit:
println("stopping f1")
return
case <-time.After(1 * time.Second):
// repeats loop
Related literature
Thank you so much for the such detailed and full answer. The question is a little bit stupid. I had thought to try the empty default:, but it looked weird. Now I see why it isn't.
– Dimaf
Sep 16 '18 at 19:35
@Dimaf another thing is that the
time.Sleep()
call mandates that quit
is only checked once per second. A better solution is to select
over quit
and a time.After
call. Notice that this one can exit on a non-whole-second interval as a result: play.golang.org/p/fPwiA34Jjnt– Kaedys
Sep 17 '18 at 17:58
time.Sleep()
quit
select
quit
time.After
Thanks @Kaedys, that's a good point. I've edited the answer to add wording to this effect and linked to your playground example.
– Cosmic Ossifrage
Sep 17 '18 at 18:43
@Kaedys, thanks. That looks as the better solution and more idiomatic Go.
– Dimaf
Sep 17 '18 at 21:08
Thanks for contributing an answer to Stack Overflow!
But avoid …
To learn more, see our tips on writing great answers.
Required, but never shown
Required, but never shown
By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy
Because there is a break in the
for
– Ullaakut
Sep 16 '18 at 17:34