为什么 Go 函数中的 go func 需要 waitgroup 才能正确退出?
Why go func in Go function needs waitgroup to exit correctly?
对不起,这个标题可能具有误导性。实际上完整的代码如下:
package main
import (
"fmt"
"sync"
)
type Button struct {
Clicked *sync.Cond
}
func main() {
button := Button{
Clicked: sync.NewCond(&sync.Mutex{}),
}
subscribe := func(c *sync.Cond, fn func()) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
wg.Done()
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
}()
wg.Wait()
}
var clickRegistered sync.WaitGroup
clickRegistered.Add(2)
subscribe(button.Clicked, func() {
fmt.Println("maximizing window")
clickRegistered.Done()
})
subscribe(button.Clicked, func() {
fmt.Println("displaying dialog")
clickRegistered.Done()
})
button.Clicked.Broadcast()
clickRegistered.Wait()
}
当我评论某些行并再次 运行 时,它会抛出一个致命错误 "all goroutines are asleep - deadlock!"
修改后的订阅函数如下所示:
subscribe := func(c *sync.Cond, fn func()) {
//var wg sync.WaitGroup
//wg.Add(1)
go func() {
//wg.Done()
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
}()
//wg.Wait()
}
让我困惑的是go func
是否在外subscribe
函数returns之前执行。在我看来,go func
将 运行 作为守护进程,尽管外部函数已经返回,所以 wg
变量是不必要的。但这表明我完全错了。那么如果go func
有不被调度的可能,是否意味着我们必须在每个函数或代码块中使用sync.WaitGroup
来确保goroutine被调度在函数或代码之前执行阻止 returns?
谢谢大家。
使用 wg
等待组(如当前组中的编码):当 subscribe
函数 returns 时,您 知道 waiting goroutine 至少已经开始执行了。
因此,当您的主函数到达 button.Clicked.Broadcast()
时,两个 goroutine 很有可能实际上正在等待它们的 button.Clicked.Wait()
调用。
没有 wg
,您无法保证 goroutine 已经启动,您的代码可能会过早调用 button.Clicked.Broadcast()
。
请注意,您使用 wg
只是降低了发生死锁的可能性,但并不能在所有情况下都阻止死锁。
尝试用 -race
和 运行 循环编译你的二进制文件(例如来自 bash : for i in {1..100}; do ./myprogram; done
),我想你会看到相同的问题有时会发生。
问题是c.Wait()
在任何一个调用中都不能保证在button.Clicked.Broadcast()
之前运行;甚至你的原始代码使用 WaitGroup
也不能保证它(因为它是 c.Wait()
部分,而不是重要的 goroutine 的产生)
修改订阅:
subscribe := func(c *sync.Cond, subWG *sync.WaitGroup, fn func()) {
go func() {
c.L.Lock()
defer c.L.Unlock()
subWG.Done() // [2]
c.Wait()
fn()
}()
}
等待代码:
subWG.Done()
button.Clicked.L.Lock()
button.Clicked.L.Unlock()
这是基于以下观察,即 [2]
只能在开始时或执行 [2]
的所有先前 goroutine 都持有 c.Wait
之后发生,因为储物柜他们分享。所以subWG.Wait()
,意思是2
(或者订阅数)[2]
被执行,只有一个goroutine没有hold住c.Wait
,可以解决下次再向 Lock
索取储物柜。
对不起,这个标题可能具有误导性。实际上完整的代码如下:
package main
import (
"fmt"
"sync"
)
type Button struct {
Clicked *sync.Cond
}
func main() {
button := Button{
Clicked: sync.NewCond(&sync.Mutex{}),
}
subscribe := func(c *sync.Cond, fn func()) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
wg.Done()
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
}()
wg.Wait()
}
var clickRegistered sync.WaitGroup
clickRegistered.Add(2)
subscribe(button.Clicked, func() {
fmt.Println("maximizing window")
clickRegistered.Done()
})
subscribe(button.Clicked, func() {
fmt.Println("displaying dialog")
clickRegistered.Done()
})
button.Clicked.Broadcast()
clickRegistered.Wait()
}
当我评论某些行并再次 运行 时,它会抛出一个致命错误 "all goroutines are asleep - deadlock!"
修改后的订阅函数如下所示:
subscribe := func(c *sync.Cond, fn func()) {
//var wg sync.WaitGroup
//wg.Add(1)
go func() {
//wg.Done()
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
}()
//wg.Wait()
}
让我困惑的是go func
是否在外subscribe
函数returns之前执行。在我看来,go func
将 运行 作为守护进程,尽管外部函数已经返回,所以 wg
变量是不必要的。但这表明我完全错了。那么如果go func
有不被调度的可能,是否意味着我们必须在每个函数或代码块中使用sync.WaitGroup
来确保goroutine被调度在函数或代码之前执行阻止 returns?
谢谢大家。
使用 wg
等待组(如当前组中的编码):当 subscribe
函数 returns 时,您 知道 waiting goroutine 至少已经开始执行了。
因此,当您的主函数到达 button.Clicked.Broadcast()
时,两个 goroutine 很有可能实际上正在等待它们的 button.Clicked.Wait()
调用。
没有 wg
,您无法保证 goroutine 已经启动,您的代码可能会过早调用 button.Clicked.Broadcast()
。
请注意,您使用 wg
只是降低了发生死锁的可能性,但并不能在所有情况下都阻止死锁。
尝试用 -race
和 运行 循环编译你的二进制文件(例如来自 bash : for i in {1..100}; do ./myprogram; done
),我想你会看到相同的问题有时会发生。
问题是c.Wait()
在任何一个调用中都不能保证在button.Clicked.Broadcast()
之前运行;甚至你的原始代码使用 WaitGroup
也不能保证它(因为它是 c.Wait()
部分,而不是重要的 goroutine 的产生)
修改订阅:
subscribe := func(c *sync.Cond, subWG *sync.WaitGroup, fn func()) {
go func() {
c.L.Lock()
defer c.L.Unlock()
subWG.Done() // [2]
c.Wait()
fn()
}()
}
等待代码:
subWG.Done()
button.Clicked.L.Lock()
button.Clicked.L.Unlock()
这是基于以下观察,即 [2]
只能在开始时或执行 [2]
的所有先前 goroutine 都持有 c.Wait
之后发生,因为储物柜他们分享。所以subWG.Wait()
,意思是2
(或者订阅数)[2]
被执行,只有一个goroutine没有hold住c.Wait
,可以解决下次再向 Lock
索取储物柜。