Home > Back-end >  Goroutine Output in Windows not finish like in Linux
Goroutine Output in Windows not finish like in Linux

Time:09-17

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

  • Related