Search code examples
gomemory-leakschannelgoroutine

Why does reading from a nil channel increase the number of Goroutines?


I'm reading this blog, https://medium.com/golangspec/goroutine-leak-400063aef468, and have adapted the following example illustrating a goroutine leak due to receiving from a nil channel:

package main

import (
    "flag"
    "fmt"
    "runtime"
    "time"
)

var initChannel bool

func main() {
    flag.Parse()

    var ch chan int
    if initChannel {
        ch = make(chan int, 1)
        ch <- 1
    }
    go func(ch chan int) {
        <-ch
    }(ch)

    c := time.Tick(1 * time.Second)
    for range c {
        fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())
    }
}

func init() {
    flag.BoolVar(&initChannel, "init", false, "initialize channel")
}

I've noticed that if I run it with initChannel false, the number of goroutines is 2:

> go run main.go
#goroutines: 2
#goroutines: 2

whereas if I run it with true, the number is 1:

> go run main.go --init
#goroutines: 1
#goroutines: 1

I don't quite understand why this is the case, however. I only see one go statement, so I would expect only one goroutine in either case. Why are there two goroutines when reading from a nil channel?


Solution

  • When your app starts, there is already one goroutine that runs the main() function.

    If you don't initialize the ch channel, it will remain its zero value which is nil for channels. Spec: Receive operator:

    Receiving from a nil channel blocks forever.

    For details, see How does a non initialized channel behave?

    So if the channel is nil, the launched goroutine will never end. So you will have 2 goroutines: the main goroutine and the one you launched.

    If you initialize the channel with 1 buffer and send one value on it, then you will also have 2 goroutines for a "short period of time", but the launched goroutine can receive a value from it and then end immediately. So there will be a single goroutine remaining, the main goroutine.