I don't understand why Goroutine in Windows didn't finish properly like Goroutine in Linux?
I already run the code in Powershell, VSCode, Goland, and even CMD, but the code never finish properly like Linux output.
below is the code:
import (
"fmt"
"time"
)
func count() {
for i := 0; i < 5; i {
fmt.Println(i)
time.Sleep(time.Millisecond * 1)
}
}
func main() {
go count()
time.Sleep(time.Millisecond * 2)
fmt.Println("Hello World")
time.Sleep(time.Millisecond * 5)
}
Windows Output:
0
1
Hello World
Linux Output (Which is expected output):
0
1
2
Hello World
3
4
Kindly help me understand or how to fix this.
p/s: I just started learning Go.
CodePudding user response:
You seem to be trying to use time.Sleep
to synchronize your go routines - in other words, so that main
doesn't end before count
.
This is the wrong way to synchronize goroutines. Keep in mind that most general purpose operating systems such as Linux and Windows do not guarantee timing; you need a real-time OS for that. So while you might usually get lucky and have the goroutines execute in the expected order, there is no guarantee that sleeps will make things happen in the expected order even on linux. The timing between these goroutines is simply not deterministic.
One of the correct ways to synchronize goroutines is with a sync.WaitGroup
.
The following code works with or without the sleeps.
package main
import (
"fmt"
"sync"
)
func count(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i {
fmt.Println(i)
}
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
go count(&wg)
fmt.Println("Hello World")
wg.Wait()
}
syc.WaitGroup
is a convenient way to make sure the all goroutines are complete, in this case before the main function exits thus ending the program.
You could also do it with a channel:
package main
import (
"fmt"
)
func count(done chan bool) {
for i := 0; i < 5; i {
fmt.Println(i)
}
done <- true
}
func main() {
var done = make(chan bool)
go count(done)
fmt.Println("Hello World")
<-done
}
CodePudding user response:
It is a well-known issue: starting from Go 1.16 time.Sleep
in MS Windows uses low resolution timer, as bad as 1/64 second. Sleeping for 1 ms in Windows is anything between 1 and 16 ms.
Try printing microsecond timestamps:
package main
import (
"fmt"
"time"
)
func count() {
fmt.Println(time.Now().Nanosecond() / 1000)
for i := 0; i < 5; i {
fmt.Println(i, time.Now().Nanosecond()/1000)
time.Sleep(time.Millisecond * 1)
}
}
func main() {
// if runtime.GOOS == "windows" {
// initTimer()
// }
ts := time.Now().UnixNano()
fmt.Println("Main started: ", ts, (ts%1_000_000_000)/1000)
go count()
time.Sleep(time.Millisecond * 2)
fmt.Println("Hello World", time.Now().Nanosecond()/1000)
time.Sleep(time.Millisecond * 5)
fmt.Println("Main done", time.Now().Nanosecond()/1000)
}
On my Windows 10:
Main started: 1663345297398120000 398120
398703
0 398703
Hello World 405757
1 405757
2 421481
Main done 421481
The numbers are microseconds. See, how big are the intervals.
To improve timer resolution you can call timeBeginPeriod
function.
//go:build windows
// build windows
package main
import "syscall"
func initTimer() {
winmmDLL := syscall.NewLazyDLL("winmm.dll")
procTimeBeginPeriod := winmmDLL.NewProc("timeBeginPeriod")
procTimeBeginPeriod.Call(uintptr(1))
}
Calling initTimer
helps a lot:
Main started: 1663345544132793500 132793
132793
0 133301
Hello World 134854
1 134854
2 136403
3 137964
4 139627
Main done 140696
Still the resolution is not 1 ms, but better than 2 ms.
The full code is here: https://go.dev/play/p/LGPv74cgN_h
Discussion thread in Golang issues: https://github.com/golang/go/issues/44343