I am trying to end multiple goroutines once another goroutine closes a channel. However, I am ending up into infinite loop after close signal is received. I can't figure out why.
I know that it is possible using context.Context but I was trying out by closing channels.
Go Playground: https://play.golang.org/p/C6pcYgGLnG9
package main
import (
"fmt"
"time"
"sync"
)
func runner(id int, ch <-chan struct{}, wg *sync.WaitGroup) {
for {
select {
case <-time.Tick(time.Second):
fmt.Println("worker ", id)
case <- ch:
fmt.Println("closing worker ", id)
break
}
}
wg.Done()
}
func main() {
fmt.Println("Hello, playground")
ch := make(chan struct{})
var wg sync.WaitGroup
wg.Add(1)
go runner(1, ch, &wg)
wg.Add(1)
go runner(2, ch, &wg)
time.Sleep(5*time.Second)
close(ch)
wg.Wait()
}
The problem is the scope of your break
:
func runner(id int, ch <-chan struct{}, wg *sync.WaitGroup) {
for {
select {
case <-time.Tick(time.Second):
fmt.Println("worker ", id)
case <- ch:
fmt.Println("closing worker ", id)
break
}
}
wg.Done()
}
You want to break out of the for loop, but you're actually only breaking out of the select
. To remedy this, you have two choices:
Add a label to your for loop, and break from it explicitly:
func runner(id int, ch <-chan struct{}, wg *sync.WaitGroup) {
loop: // <---------- add a label
for {
select {
case <-time.Tick(time.Second):
fmt.Println("worker ", id)
case <-ch:
fmt.Println("closing worker ", id)
break loop // <---------- and break from it explicitly
}
}
wg.Done()
}
Probably a more idiomatic and robust solution, simply return when you're done. This means the wg.Done()
call must be deferred.
func runner(id int, ch <-chan struct{}, wg *sync.WaitGroup) {
defer wg.Done() // <--- Defer the wg.Done() call, so it happens on return
for {
select {
case <-time.Tick(time.Second):
fmt.Println("worker ", id)
case <-ch:
fmt.Println("closing worker ", id)
return // <--- replace `break` with `return`
}
}
}