Multithreading 为什么go函数中的go func需要waitgroup才能正确退出?
Sry这个标题可能有误导性。实际上,完整代码如下所示:Multithreading 为什么go函数中的go func需要waitgroup才能正确退出?,multithreading,go,synchronization,race-condition,goroutine,Multithreading,Go,Synchronization,Race Condition,Goroutine,Sry这个标题可能有误导性。实际上,完整代码如下所示: package main import ( "fmt" "sync" ) type Button struct { Clicked *sync.Cond } func main() { button := Button{ Clicked: sync.NewCond(&sync.Mutex{}), } subscribe :=
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()
}
当我注释一些行并再次运行它时,它会抛出一个致命错误“所有goroutines都处于休眠状态-死锁!”修改后的订阅功能如下所示:
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()
}
让我困惑的是,是否在外部subscribe
函数返回之前执行go func
。在我看来,go func
将作为守护进程运行,尽管外部函数已返回,因此不需要wg
变量。但这表明我完全错了。因此,如果go func
有可能没有被调度,这是否意味着我们必须在每个函数或代码块中使用sync.WaitGroup
,以确保在函数或代码块返回之前调度执行goroutine?谢谢大家。使用
wg
waitgroup(按照当前组中的编码):当subscribe
函数返回时,您知道等待的goroutine至少已开始执行
因此,当您的主函数到达按钮.Clicked.Broadcast()
时,两个goroutine很有可能正在等待它们的按钮.Clicked.Wait()
调用
如果没有wg
,您无法保证goroutines已经启动,您的代码可能会过早地调用按钮.Clicked.Broadcast()
请注意,使用
wg
只会降低死锁发生的可能性,但不会在所有情况下都阻止死锁
尝试使用
-race
编译二进制文件,并在循环中运行它(例如,从bash:for i in{1..100};do./myprogram;done
),我想您会看到有时也会出现同样的问题。使用wg
waitgroup(在当前组中编码):当subscribe
函数返回时,你知道等待的goroutine至少已经开始执行了
因此,当您的主函数到达按钮.Clicked.Broadcast()
时,两个goroutine很有可能正在等待它们的按钮.Clicked.Wait()
调用
如果没有wg
,您无法保证goroutines已经启动,您的代码可能会过早地调用按钮.Clicked.Broadcast()
请注意,使用
wg
只会降低死锁发生的可能性,但不会在所有情况下都阻止死锁
尝试使用
-race
编译二进制文件,并在循环中运行它(例如,从bash开始:for i in{1..100};do./myprogram;done
),我想您会看到有时也会出现同样的问题。问题是,在按钮之前,两个调用中的c.Wait()
都不能保证运行;甚至您的原始代码使用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没有保持c.Wait
,这可以通过另一次请求锁柜锁定来解决
游乐场:问题是,在按钮之前,两个调用中的c.Wait()
都不能保证运行;甚至您的原始代码使用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没有保持c.Wait
,这可以通过另一次请求锁柜锁定来解决
操场:谢谢@LeGEC我明白你的意思了。但是为什么我使用的wg
仍然存在问题?您的意思是当主goroutine到达按钮。单击.Broadcast()
调用时,两个goroutine可能没有到达c.Wait()
?i、 e.广播
在1或2个goroutines等待单击的sync.Cond
之前被调用。我说得对吗?谢谢你的耐心。@shengjiang:是的,没错。看起来您正在编写一些测试代码,所以我不会尝试构建太复杂的东西:一个简单的解决方法(它仍然不完整,但在实践中会很好地工作),就是在调用.Broadcast()
:之前等待一段时间,非常感谢!我正在学习多线程编程。我在c.L.Lock()
下面添加了time.Sleep(1*time.Second)
,死锁按预期再次发生。再次感谢你的帮助@盛江:检查@leadbebebop的答案,他正确地建议在主函数中获取cond.L.Lock()
,以确保其他goroutine是。Wait()
在该条件下运行谢谢@LeGEC我明白了你的意思。但是为什么我使用的wg
仍然存在问题?您的意思是当主goroutine到达按钮。单击.Broadcast()
调用时,两个goroutine可能没有到达c.Wait()
?i、 e.在之前调用广播