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?
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
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:
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.
break
applies to the select
statementHowever, 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
will (probably) not immediately stop the programAs 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.
More idiomatic Go would block in the select
statement on receiving from two channels:
quit
channeltime.After
– this call is an abstraction around a Timer
which sleeps for a period of time and then writes a value to the provided channel.A resolution to fix your immediate issue with minimal changes to your code would be:
quit
non-blocking by adding a default case to the select
statement.quit
succeeds:
for
loop and use a labelled call to break
; orreturn
from the f1
function when it is time to quit (preferred)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
.
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.)
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:
for {
println("f1 is working...")
select {
case <-quit:
println("stopping f1")
return
case <-time.After(1 * time.Second):
// repeats loop
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With