Golang中WaitGroup的使用问题求助 - 附代码示例play.golang.org/p/HvHQbUTT6GJ
Golang中WaitGroup的使用问题求助 - 附代码示例play.golang.org/p/HvHQbUTT6GJ 在这段代码中,https://play.golang.org/p/HvHQbUTT6GJ,为什么 bar(用这个词是否正确?)会在 foo 之前执行?
我想目前这样就挺好的。你在另一个话题中提到过标记问题为已解决?我该如何操作?如果标记了,以后还能回来查看学习吗?
更多关于Golang中WaitGroup的使用问题求助 - 附代码示例play.golang.org/p/HvHQbUTT6GJ的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
hollowaykeanho:
启动另一个进程
这个短语本身就很有意思,我在谷歌上搜索时得到了一些有趣的定义。
hollowaykeanho:
关于并发的问题
非常有趣。
这并不一定需要。
实际上在这种情况下,foo() 和 bar() 的执行顺序是未定义的,理论上也可能交替执行。
(不过在 Playground 上总是会产生这样的输出,因为该环境的设置方式使得调度或一般的随机性具有可重现性)
hollowaykeanho:
收到指示要求在
foo()执行后立即执行bar()
因此在我看来,输出结果中 foo 应该会先出现。
尝试使用引用按钮时,这个回复不知何故出现在我对 NobbZ 的回复之前。
cherilexvold1974:
标记问题为已解决?我该怎么做
你可以查看所有回复的帖子,找出最合适的答案。然后注意每个回复旁边都有一个复选框。勾选该复选框即可将该回复标记为你问题的解决方案。

cherilexvold1974:
我以后还能回来查看吗?
你确实可以收藏自己的问题,可以通过浏览器收藏夹或论坛收藏功能。进入问题页面并展开选项(三个点),那里有一个收藏图标。点击它即可。
NobbZ:
也可能是交错执行的。
这让我感到困惑。这具体是什么意思?
通常可重现的随机性
你的意思是执行顺序是随机的吗?也就是说 foo 可能在 bar 之前运行,也可能 bar 在 foo 之前运行?
hollowaykeanho:
@Noobz,启动顺序在代码中是有明确规定的,具体是第18行(委托)然后到第19行(执行
bar())。然而,在单核CPU中,main和foo这两个goroutine的并发执行顺序是不可预测的。
所以这听起来像是执行顺序是随机的。
或者这是否…
hollowaykeanho:
启动顺序在代码中是有明确规定的,具体是第18行(委托)然后到第19行(执行
bar())
意味着执行顺序并非随机?
难道
go foo()
是以某种方式告诉 foo 在 bar 之后运行吗?
cherilexvold1974:
那么这听起来执行是随机的。
或者这是否…
cherilexvold1974:
是否以某种方式
go foo()告诉 foo 在 bar 之后运行?
go foo() 并不是说必须在 bar() 之后运行,而是启动另一个进程来运行 foo()。在运行实际的 foo() 之前,这个启动过程有它自己的元进程需要初始化。这就是为什么与已建立的进程 bar() 相比,foo() 会稍后出现。
另外,请记住,使用 goroutine 委托进程并不能保证它会立即启动。这就是为什么 foo() 看起来启动较晚,但我无法确定它需要多长时间才能开始执行 foo()。
执行是随机的,因为无法确定在单个 CPU 上同时运行的是哪个进程(bar 或 foo),所以输出是混乱的。请注意 Playground 的输出和我引用的输出之间的差异。这些输出并不一致。
至于原因,你可以参考这个帖子:关于并发的问题。
bar首先运行主要是因为main()进程仍在运行,并且在foo()委托(或实际术语:分叉)之后立即被指示执行bar()。请记住,main()本身仍然是一个进程。
当一个新的进程被分叉时,与已经是运行中的进程的
main()进程相比,它需要一些(编辑:不可预测的)时间来设置然后执行。
事实上,在这种情况下,
foo()和bar()的执行顺序是未定义的,理论上也可能是交错的。
很好的提出了那个重要的观点。谢谢! 😅
我在本地系统中运行了你的代码,它们始终一致:bar() 在 foo() 之前。但是请注意,在并发中,它们永远不会像在 Playground 中那样完美结束。以下是我本地系统的一次运行结果(经过 20 次运行后):
OS linux
ARCH amd64
CPUs 8
Goroutines 1
bar: 0
bar: 1
bar: 2
bar: 3
bar: 4
bar: 5
bar: 6
bar: 7
bar: 8
bar: 9
CPUs 8
foo: 0
foo: 1
foo: 2
foo: 3
foo: 4
foo: 5
foo: 6
foo: 7
foo: 8
foo: 9
Goroutines 2
请记住,并发不是并行。那是完全不同的概念。
@Noobz,启动顺序在代码中是有定义的,具体是第 18 行(委托)然后到第 19 行(执行 bar())。然而,在单个 CPU 上,main 和 foo 协程并发执行的方式是不可预测的。
在您提供的代码示例中,bar 函数确实会在 foo 函数之前执行,这是因为 WaitGroup 的使用方式存在问题。具体来说,wg.Add(1) 的调用时机不正确,导致主 goroutine 在等待之前,bar 的 goroutine 可能已经完成。
以下是代码的简要分析:
- 在
main函数中,wg.Add(1)被调用一次,然后启动foo的 goroutine。 - 在
foo函数中,又调用了wg.Add(1)并启动bar的 goroutine。 - 问题在于,
wg.Add(1)在foo的 goroutine 中被调用,这可能导致主 goroutine 在wg.Wait()时,bar的 goroutine 还没有被添加到 WaitGroup 中(如果foo的 goroutine 尚未执行到wg.Add(1)部分)。但在实际执行中,由于 goroutine 调度和延迟,bar的 goroutine 可能先于foo的 goroutine 执行完成,导致bar的输出先出现。
正确的做法是在启动任何 goroutine 之前,先调用 wg.Add 来设置等待的 goroutine 数量。以下是修正后的代码示例:
package main
import (
"fmt"
"sync"
"time"
)
func foo(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("foo")
}
func bar(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("bar")
}
func main() {
var wg sync.WaitGroup
wg.Add(2) // 在启动 goroutine 前添加两个等待
go foo(&wg)
go bar(&wg)
wg.Wait() // 等待两个 goroutine 完成
}
在这个修正版本中:
- 我们在
main函数中调用wg.Add(2),明确指定要等待两个 goroutine(foo和bar)。 - 然后启动两个 goroutine,每个 goroutine 在完成时调用
wg.Done()。 - 这样,
wg.Wait()会正确等待两个 goroutine 都执行完毕,但执行顺序仍然依赖于 goroutine 调度(因此输出顺序可能不同,例如可能先输出 “bar” 后输出 “foo”,或反之)。
如果您希望控制执行顺序,需要使用其他同步机制(如通道或互斥锁),而不是依赖 WaitGroup。WaitGroup 仅用于等待一组 goroutine 完成,不保证执行顺序。


