我遇到了以下代码段,它们演示了sync.Cond中的“广播”功能。该代码段如下:
package main
import (
"fmt"
"sync"
)
func main() {
type Button struct {
Clicked *sync.Cond
}
button := Button{Clicked: sync.NewCond(&sync.Mutex{})}
subscribe := func(c *sync.Cond, fn func()) {
var goroutineRunning sync.WaitGroup
goroutineRunning.Add(1)
go func() {
goroutineRunning.Done()
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
}()
goroutineRunning.Wait()
}
var clickRegistered sync.WaitGroup
clickRegistered.Add(3)
subscribe(button.Clicked, func() {
fmt.Println("Maximizing window.")
clickRegistered.Done()
})
subscribe(button.Clicked, func() {
fmt.Println("Displaying annoying dialogue box!")
clickRegistered.Done()
})
subscribe(button.Clicked, func() {
fmt.Println("Mouse clicked.")
clickRegistered.Done()
})
button.Clicked.Broadcast()
clickRegistered.Wait()
}
输出如下:
Mouse clicked.
Maximizing window.
Displaying annoying dialogue box!
我更改了订阅中的goroutine,以在goroutine完成执行后推迟对gorroutineRunning等待组的'Done'调用。我的想法是,等待组仅应在执行go例程完成后才递减。因此,我将代码更改如下:
package main
import (
"fmt"
"sync"
)
func main() {
......
subscribe := func(c *sync.Cond, fn func()) {
var goroutineRunning sync.WaitGroup
goroutineRunning.Add(1)
go func() {
//Adding the defer here
defer goroutineRunning.Done()
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
}()
goroutineRunning.Wait()
}
....
}
加上延后,我会感到以下恐慌:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc0000b6028)
/usr/local/go/src/runtime/sema.go:56 +0x42
sync.(*WaitGroup).Wait(0xc0000b6020)
/usr/local/go/src/sync/waitgroup.go:130 +0x64
main.main.func1(0xc00009e040, 0xc0000b4030)
/Users/go/concur/button.go:24 +0x91
main.main()
/Users/go/concur/button.go:29 +0xf4
goroutine 18 [sync.Cond.Wait]:
runtime.goparkunlock(...)
/usr/local/go/src/runtime/proc.go:310
sync.runtime_notifyListWait(0xc00009e050, 0x0)
/usr/local/go/src/runtime/sema.go:510 +0xf8
sync.(*Cond).Wait(0xc00009e040)
/usr/local/go/src/sync/cond.go:56 +0x9d
main.main.func1.1(0xc0000b6020, 0xc00009e040, 0xc0000b4030)
/Users/go/concur/button.go:21 +0xbb
created by main.main.func1
/Users/go/concur/button.go:17 +0x83
exit status 2
有人可以引导我理解为什么添加延迟会导致代码崩溃吗?
原始代码在goroutine开始运行后立即释放等待组。当subscribe
函数返回时,goroutine仍处于活动状态。
[将其更改为defer goroutineRunning.Done()
时,goroutine开始,在c.Wait()
处停止,因为它正在等待条件变量的广播。由于goroutine正在等待,因此不会调用goroutineRunning.Done
,subscribe
功能会在goroutineRunning.Wait
处停止。因此,首次调用订阅时,它将创建一个等待cond的goroutine,订阅本身将开始在waitgroup中等待。有goroutines(主要程序和一个由subscribe开头的程序),都等待某个事件发生,但是没有其他goroutines运行以使该事件发生,所以出现了死锁。