Reputation: 45295
I am reading 'Concurrency in Go' and have found this example of goroutine leaks:
func main() {
var wg sync.WaitGroup
doWork := func(strings <-chan string) <-chan interface{} {
completed := make(chan interface{})
go func() {
defer fmt.Println("doWork exited.")
defer close(completed)
defer wg.Done()
fmt.Println("a")
for s := range strings {
fmt.Println(s)
}
fmt.Println("b")
}()
return completed
}
wg.Add(1)
doWork(nil)
fmt.Println("Waiting")
wg.Wait()
fmt.Println("Done.")
}
The
strings
channel will never gets any strings written onto it, and the goroutine containingdoWork
will remain in memory for the life time of process.
I don't understand - why ?
How I understand this code:
As strings
is nil
range
-loop just skipped. As any range over nil
:
slice := []int{10, 20, 30, 40, 50}
slice = nil
for i := range slice {
fmt.Println(i)
}
fmt.Println("Done")
fmt.Println("doWork exited.")
will be executed
close(completed)
will be executedBut I see it works like that. Why ?
Upvotes: 2
Views: 250
Reputation: 38004
As strings is nil range-loop just skipped.
This assumption is not correct. In Go, reading from a nil
channel will always block. This is defined in the language specification (thanks to @peterSO for digging out the link):
Receiving from a nil channel blocks forever.
There's also a post on the Go Design Patterns blog that further elaborates on this behaviour and highlights some cases in which it is useful.
Anyhow, this behaviour can easily be reproduced with a minimal example (playground):
func main() {
var s chan string
<- s
}
This program will never finish (in the playground, it will crash with all goroutines are asleep - deadlock
).
Because reading from a nil channel (in the case of your example, strings
) will block (forever, since nothing can be written into a nil channel), the doWork
goroutine will never complete, and thus, leak.
Upvotes: 4