Search code examples
gogoroutine

Goroutine never executed on Linux but work normally on Windows


I am new to Go. After hours on google still could not find the explanation of this problem. The code below run normally on Windows. It run:

0
2
3
2A

and it wait forever, but the same code not working on Linux (Ubuntu), the goroutine never executed:

0
2
3

and it wait forever.

package main

import ("fmt")
var exit bool=false

func main(){
   fmt.Println("0")
   fmt.Println("2")
   go func() {
      fmt.Println("2A");
   }()
   fmt.Println("3")
   for {
        if exit {
           break
        }
   }
   fmt.Println("4")
}

Environment: Linux Ubuntu, go1.10.3 linux/amd64


Solution

  • There is a simple explanation. It has nothing to do with operating systems. The behavior is the same on Linux and Windows.

    $ go version
    go version devel +f2ed3e1da1 Sat Aug 25 18:36:22 2018 +0000 linux/amd64
    $
    
    >go version
    go version devel +e03220a594 Sat Aug 25 02:39:49 2018 +0000 windows/amd64
    >
    

    The main goroutine will effectively stop when it encounters the busy-wait spin loop:

    for {
        if exit {
            break
        }
    }
    

    If we are only using one core then the entire program will stop. Otherwise, other goroutines can continue to run. The number of cores used is controlled by GOMAXPROCs up to a usable maximum of NumCPU.

    cores.go:

    package main
    
    import (
        "fmt"
        "runtime"
    )
    
    var exit bool = false
    
    func main() {
        fmt.Println("NumCPU:    ", runtime.NumCPU())
        if runtime.GOMAXPROCS(0) > runtime.NumCPU() {
            runtime.GOMAXPROCS(runtime.NumCPU())
        }
        fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0))
    
        fmt.Println("0")
        fmt.Println("2")
        go func() {
            fmt.Println("2A")
        }()
        fmt.Println("3")
        for {
            if exit {
                break
            }
        }
        fmt.Println("4")
    }
    

    Using one core, the Go scheduler ran the main goroutine. It didn't get a chance to run the 2A goroutine.

    $ env GOMAXPROCS=1 go run cores.go
    NumCPU:     4
    GOMAXPROCS: 1
    0
    2
    3
    ^Csignal: interrupt
    $ 
    

    Using more than one core, the Go scheduler ran the main and 2A goroutines.

    $ env GOMAXPROCS=2 go run cores.go
    NumCPU:     4
    GOMAXPROCS: 2
    0
    2
    3
    2A
    ^Csignal: interrupt
    $ 
    

    Now, let's surrender the main goroutine with a Gosched() just before the spin loop to give other goroutines a chance to run.

    gosched.go:

    package main
    
    import (
        "fmt"
        "runtime"
    )
    
    var exit bool = false
    
    func main() {
        fmt.Println("NumCPU:    ", runtime.NumCPU())
        if runtime.GOMAXPROCS(0) > runtime.NumCPU() {
            runtime.GOMAXPROCS(runtime.NumCPU())
        }
        fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0))
    
        fmt.Println("0")
        fmt.Println("2")
        go func() {
            fmt.Println("2A")
        }()
        fmt.Println("3")
        runtime.Gosched()
        for {
            if exit {
                break
            }
        }
        fmt.Println("4")
    }
    

    Sharing one core, the main and 2A goroutines ran.

    $ env GOMAXPROCS=1 go run gosched.go
    NumCPU:     4
    GOMAXPROCS: 1
    0
    2
    3
    2A
    ^Csignal: interrupt
    $
    

    Sharing two cores, the main and 2A goutines ran.

    $ env GOMAXPROCS=2 go run gosched.go
    NumCPU:     4
    GOMAXPROCS: 2
    0
    2
    3
    2A
    ^Csignal: interrupt
    $ 
    

    NOTE: The Go scheduler behavior will likely change as the Go runtime is improved. For example, spin locks may not be allowed to monopolize a core.