Reputation: 1253
I'm following this code to get a lazy range of numbers with channels
// iterator
func iterator(n int, c chan int) {
for i := 0; i < n; i++ {
c <- i
}
close(c)
fmt.Println("iterator End")
}
c := make(chan int)
go iterator(5, c)
for i := range c {
fmt.Println(i)
}
This will print as expected
0
1
2
3
4
fmt.Println("iterator End")
But what happened when I break the for loop like this
c := make(chan int)
go getNumbers(5, c)
for i := range c {
if i == 2 {
break
}
fmt.Println(i)
}
It seems the goroutine is blocked because never prints iterator End
(I also try by sleeping the main thread).
I'm wondering how to handle this scenario?
Did I need to use select
to resolve this?
There is any safe way to check if the range was break and stop the for-loop in the iterator?
Upvotes: 1
Views: 977
Reputation: 22117
If a goroutine writes to an unbuffered channel and no other goroutine is reading from the channel - then the writes will block forever. This will cause a goroutine leak. This is what you are experiencing.
If you have a "producer" goroutine which writes to a channel, you need a way to signal it to stop. Closing the channel is not the critical part here - as channels are garbage collected when they go out of scope. A blocked goroutine (that will never unblock) is considered a leak as it will never be reclaimed, so you really need the goroutine to end.
You can signal an intent to quit in many ways - the two most popular being:
done
channel; orfunc iterator(n int, c chan int, done <-chan struct{}) {
for i := 0; i < n; i++ {
select {
case c <- i:
case <-done:
break
}
}
close(c)
fmt.Println("iterator End")
}
reader goroutine:
c := make(chan int)
done := make(chan struct{})
go iterator(5, c, done)
for i := range c {
if i == 2 {
break
}
fmt.Println(i)
}
close(done) // signal writer goroutine to quit
func iterator(ctx context.Context, n int, c chan int) {
defer close(c)
defer fmt.Println("iterator End")
for i := 0; i < n; i++ {
select {
case c <- i:
case <-ctx.Done():
fmt.Println("canceled. Reason:", ctx.Err())
return
}
}
}
read goroutine:
func run(ctx context.Context) {
ctx, cancel := context.WithCancel(ctx)
defer cancel() // call this regardless - avoid context leaks - but signals producer your intent to stop
c := make(chan int)
go iterator(ctx, 5, c)
for i := range c {
if i == 2 {
break
}
fmt.Println(i)
}
}
https://play.golang.org/p/4-fDyCurB7t
Upvotes: 4