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
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.