Tumblelog by Soup.io
Newer posts are loading.
You are at the newest post.
Click here to check if anything new just came in.

Sankar P: golang range Tickers

Update: Please use the playground/gist urls for reading code. Blogger's code formatting is terrible and does not support embedding gists either.

Yesterday Praveen sent me an interesting piece of golang code. Read the following code and tell what the answer will be:

===
type LED struct {
state  bool
ticker *time.Ticker
}

func toggle(led *LED) {
led.state = !led.state
}

func looper(led *LED) {
for range led.ticker.C {
toggle(led)
}
}

func main() {

fmt.Println("Initial number of GoRoutines: ", runtime.NumGoroutine())

led := &LED{state: true, ticker: time.NewTicker(time.Millisecond * 500)}
go looper(led)
fmt.Println("Number of GoRoutines after a call to looper: ", runtime.NumGoroutine())

time.Sleep(2 * time.Second)

led.ticker.Stop()
fmt.Println("Number of GoRoutines after stopping the ticker: ", runtime.NumGoroutine())

runtime.GC()
fmt.Println("Number of GoRoutines after gc: ", runtime.NumGoroutine())

}
===


Golang playground URL: https://play.golang.org/p/1as5QN1r2c
Gist URL: https://gist.github.com/psankar/8af76ba183b0203ec141bca8156f5955

I will explain roughly what the code is doing.

There is a LED struct which has a Ticker and a state variable. While creating an instance of the led struct, we initialise the state and the Ticker.  There is a looper function will toggle the state, whenever the Ticker fires an event.

Now when the program is launched, there will be one goroutine (the initial main thread). After we call looper in a goroutine, the goroutineCount will be 2. Now, comes the tricky part. We stop the Ticker, after a particular amount of time. We even call the gc.

It was observed by Praveen that this piece of code was leaking go routines and the number of go routines was never going down, inspite of the Ticker getting stopped.

The reason why the leakage is happening is because, the "range" loop is never exiting. If the range loop was on a channel, you could "close" it. The ticker.C channel however is a receive only channel and you cannot close it.

How do we fix this, so that none of the goroutines are leaking ? If you have watched the talks, golang concurrency patterns by Rob Pike and Advanced golang concurrency patterns by Sameer Ajmani, then you will realise that it is quite easy to add another parameter to the looper function, which could just exit the loop. So the updated code will be:

===
type LED struct {
state  bool
ticker *time.Ticker
}

func toggle(led *LED) {
led.state = !led.state
}

func looper(led *LED) {
for range led.ticker.C {
toggle(led)
}
}

func looper2(led *LED, q chan bool) {
for {
select {
case <-led.ticker.C:
toggle(led)
case <-q:
fmt.Println("Exiting the goroutine")
return
}
}
}

func main() {

fmt.Println("Initial number of GoRoutines: ", runtime.NumGoroutine())

led := &LED{state: true, ticker: time.NewTicker(time.Millisecond * 500)}
q := make(chan bool)
go looper2(led, q)
// go looper(led)
fmt.Println("Number of GoRoutines after a call to looper: ", runtime.NumGoroutine())

time.Sleep(2 * time.Second)

led.ticker.Stop()
fmt.Println("Number of GoRoutines after stopping the ticker: ", runtime.NumGoroutine())

q <- true
        fmt.Println("Number of GoRoutines after sending a message on the quit channel: ", runtime.NumGoroutine())
}
===


Playground URL: https://play.golang.org/p/NlWbyHLHvA
Gist URL: https://gist.github.com/psankar/4e5b2e563038ce3e9c17eb208c76168a

Let me know if you have any comments.

Don't be the product, buy the product!

Schweinderl