在Windows中,goroutine的输出不像在Linux中那样完成。

huangapple go评论78阅读模式
英文:

Goroutine Output in Windows not finish like in Linux

问题

我不明白为什么在Windows中的Goroutine无法像Linux中的Goroutine一样正确结束。我已经在Powershell、VSCode、Goland甚至CMD中运行了这段代码,但是代码从未像Linux输出那样正确结束。

以下是代码:

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输出:

0
1
Hello World

Linux输出(预期输出):

0
1
2
Hello World
3
4

请帮助我理解或修复这个问题。

附注:我刚开始学习Go语言。

英文:

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 (
	&quot;fmt&quot;
	&quot;time&quot;
)

func count() {
	for i := 0; i &lt; 5; i++ {
		fmt.Println(i)
		time.Sleep(time.Millisecond * 1)
	}
}

func main() {
	go count()
	time.Sleep(time.Millisecond * 2)
	fmt.Println(&quot;Hello World&quot;)
	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.

答案1

得分: 2

你似乎正在尝试使用time.Sleep来同步你的Go协程,换句话说,确保maincount之前不会结束。

这是错误的同步Go协程的方式。请记住,大多数通用操作系统(如Linux和Windows)不保证时间性;你需要一个实时操作系统来实现这一点。因此,虽然你可能通常会幸运地按预期顺序执行Go协程,但不能保证睡眠会按预期顺序发生,即使在Linux上也是如此。这些Go协程之间的时间间隔是不确定的。

同步Go协程的一种正确方式是使用sync.WaitGroup

以下代码可以在有或没有睡眠的情况下工作。

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()
}

sync.WaitGroup是一种方便的方式,可以确保所有的Go协程都完成,在这种情况下,确保在主函数退出之前结束程序。

你也可以使用通道来实现:

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
}
英文:

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 (
	&quot;fmt&quot;
	&quot;sync&quot;
)

func count(wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i &lt; 5; i++ {
		fmt.Println(i)
	}
}

func main() {
	var wg sync.WaitGroup
	wg.Add(1)
	go count(&amp;wg)
	fmt.Println(&quot;Hello World&quot;)
	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 (
	&quot;fmt&quot;
)

func count(done chan bool) {
	for i := 0; i &lt; 5; i++ {
		fmt.Println(i)
	}
	done &lt;- true
}

func main() {
	var done = make(chan bool)
	go count(done)
	fmt.Println(&quot;Hello World&quot;)
	&lt;-done
}

答案2

得分: 0

这是一个众所周知的问题:从Go 1.16开始,MS Windows上的time.Sleep使用低分辨率定时器,最差只有1/64秒。在Windows上睡眠1毫秒的时间可能在1到16毫秒之间。

尝试打印微秒级时间戳:

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)
}

在我的Windows 10上运行结果为:

Main started:  1663345297398120000 398120
398703
0 398703
Hello World 405757
1 405757
2 421481
Main done 421481

这些数字是微秒。可以看到时间间隔有多大。

为了改善定时器的分辨率,你可以调用timeBeginPeriod函数。

//go:build windows
// +build windows

package main

import "syscall"

func initTimer() {
	winmmDLL := syscall.NewLazyDLL("winmm.dll")
	procTimeBeginPeriod := winmmDLL.NewProc("timeBeginPeriod")
	procTimeBeginPeriod.Call(uintptr(1))
}

调用initTimer会有很大帮助:

Main started:  1663345544132793500 132793
132793
0 133301
Hello World 134854
1 134854
2 136403
3 137964
4 139627
Main done 140696

分辨率仍然不是1毫秒,但比2毫秒要好。

完整的代码在这里:https://go.dev/play/p/LGPv74cgN_h

Golang问题讨论线程:https://github.com/golang/go/issues/44343

英文:

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 (
	&quot;fmt&quot;
	&quot;time&quot;
)

func count() {
	fmt.Println(time.Now().Nanosecond() / 1000)
	for i := 0; i &lt; 5; i++ {
		fmt.Println(i, time.Now().Nanosecond()/1000)
		time.Sleep(time.Millisecond * 1)
	}
}

func main() {
	// if runtime.GOOS == &quot;windows&quot; {
	// 	initTimer()
	// }
	ts := time.Now().UnixNano()
	fmt.Println(&quot;Main started: &quot;, ts, (ts%1_000_000_000)/1000)
	go count()
	time.Sleep(time.Millisecond * 2)
	fmt.Println(&quot;Hello World&quot;, time.Now().Nanosecond()/1000)
	time.Sleep(time.Millisecond * 5)
	fmt.Println(&quot;Main done&quot;, 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 &quot;syscall&quot;

func initTimer() {
	winmmDLL := syscall.NewLazyDLL(&quot;winmm.dll&quot;)
	procTimeBeginPeriod := winmmDLL.NewProc(&quot;timeBeginPeriod&quot;)
	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

huangapple
  • 本文由 发表于 2022年9月16日 22:19:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/73746214.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定