“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






Because there is a break in the for

– Ullaakut
Sep 16 '18 at 17:34


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

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)