Search code examples
timergoblockinggoroutine

golang timer blocking in goroutines


Below code is from go by example - timers

package main

import (
    "time"
    "fmt"
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    timer1 := time.NewTimer(time.Second * 1)

    <-timer1.C
    fmt.Println("Timer 1 expired")

    timer2 := time.NewTimer(300) //change the duration to be more shorter
    go func() {
        <-timer2.C
        fmt.Printf("Timer 2 expired")
    }()

    stop2 := timer2.Stop()
    if stop2 {
        fmt.Printf("Timer 2 stopped")
    }
}

If I run above code, the output will be like(result one):

Timer 1 expired
Timer 2 stopped

but if I change the body of the anonymous func to be:

fmt.Printf("Timer 2 expired")
<-timer2.C

the output is still like before. I'm confused, why the second output is not like(result two):

Timer 1 expired
Timer 2 expired
Timer 2 stopped

As per my understanding <-timer2.C blocks the remain of goroutine until the timer channel get a value, so if I put fmt.Printf("Timer 2 expired") after <-timer2.C the output will like result one, but if I put fmt.Printf("Timer 2 expired") before <-timer2.C, I think the print action will not be blocked.

Hope someone could give me a hand, thank you all.


Solution

  • The problem is likely that there's no guaranteed "happens before" relationship between the print statement and the end of the program. When the main Goroutine exits, the whole program exits.

    Goroutines have a startup time, and the runtime has several criteria for when it switches to another running goroutine. What's probably happening is that no code in the anonymous function is ever being executed, because the main goroutine, lacking any blocking operations (or even expensive function calls), exits very quickly

    Changing GOMAXPROCS, as this program attempts to do, can sometimes "fix" that, in that the multiple threads have a chance of sneaking in some code because they don't have to rely on an explicit context switch from the runtime, but without a guaranteed "happens before" relationship, or at least without some statement to intentionally make the main goroutine hang for a while (e.g. the empty select{}, for{} etc) you can't rely on any code outside the main goroutine ever actually running.

    It's entirely possible that on a completely different machine (or even the same machine with less load, or overclocked or...), you'd get the behavior that you expect. Unfortunately since, as you learned, you can't count on it, make sure you synchronize your goroutines.