对 goroutine 中的 defer 感到困惑
Confused about defer in goroutines
我发现了以下代码片段,它演示了 sync.Cond 中的 'broadcast' 功能。片段如下:
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' 调用。我的想法是 waitgroup 应该只在 go routine 执行完毕后递减。所以我改了代码如下:
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
函数returns时,goroutine是存活的
当您将其更改为 defer goroutineRunning.Done()
时,goroutine 开始,并在 c.Wait()
处停止,因为它正在等待条件变量广播。由于 goroutine 正在等待 goroutineRunning.Done
未被调用,并且 subscribe
函数在 goroutineRunning.Wait
处停止。所以你第一次调用 subscribe 时,它会创建一个等待 cond 的 goroutine,subscribe 本身开始等待 waitgroup。有 goroutines(主要的和由 subscribe 启动的那个),都在等待某个事件发生,但是没有其他 goroutines 运行 使该事件发生,所以死锁。
我认为您认为 goroutineRunning WaitGroup 的使用存在问题是正确的。
我认为原始代码不包括 WaitGroup 但是编写代码的人发现在 3 个 go-routines称为等待()。使用 goroutineRunning 试图解决这个问题,但它并不能避免竞争条件,只会降低它发生的可能性。例如,如果你在 goroutineRunning.Done() 之后睡眠,那么你会遇到同样的问题 - 在 3 个 go-routines 等待之前调用 Broadcast()。
回到你最初的问题......向前移动 goroutine.Done()(Wait 调用之后的任何地方)将导致死锁,因为 c.Wait() 调用必须等待Broadcast() 永远不会出现,因为第一个 subscribe() 永远不会 return 直到 goroutineRunning.Done() 被调用(解除阻塞 goroutineRunning.Wait())。
将 goroutineRunning.Done() 移动到 c.Wait() 之前更好,但不会消除竞争。
要修复原始代码,您需要在调用 c.L.Lock() 之后放置 gorutineRunning.Done() 并锁定 Broadcast()。
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() {
c.L.Lock()
defer c.L.Unlock()
goroutineRunning.Done() // *** moved
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 dialog box!")
clickRegistered.Done()
})
subscribe(button.Clicked, func() {
fmt.Println("Mouse clicked.")
clickRegistered.Done()
})
button.Clicked.L.Lock() // *** new
button.Clicked.Broadcast()
button.Clicked.L.Unlock() // *** new
clickRegistered.Wait()
[顺便说一句,我承认这不是您最初问题的答案(其他答案应该得到分数),但我认为这值得一提。]
我发现了以下代码片段,它演示了 sync.Cond 中的 'broadcast' 功能。片段如下:
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' 调用。我的想法是 waitgroup 应该只在 go routine 执行完毕后递减。所以我改了代码如下:
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
函数returns时,goroutine是存活的
当您将其更改为 defer goroutineRunning.Done()
时,goroutine 开始,并在 c.Wait()
处停止,因为它正在等待条件变量广播。由于 goroutine 正在等待 goroutineRunning.Done
未被调用,并且 subscribe
函数在 goroutineRunning.Wait
处停止。所以你第一次调用 subscribe 时,它会创建一个等待 cond 的 goroutine,subscribe 本身开始等待 waitgroup。有 goroutines(主要的和由 subscribe 启动的那个),都在等待某个事件发生,但是没有其他 goroutines 运行 使该事件发生,所以死锁。
我认为您认为 goroutineRunning WaitGroup 的使用存在问题是正确的。
我认为原始代码不包括 WaitGroup 但是编写代码的人发现在 3 个 go-routines称为等待()。使用 goroutineRunning 试图解决这个问题,但它并不能避免竞争条件,只会降低它发生的可能性。例如,如果你在 goroutineRunning.Done() 之后睡眠,那么你会遇到同样的问题 - 在 3 个 go-routines 等待之前调用 Broadcast()。
回到你最初的问题......向前移动 goroutine.Done()(Wait 调用之后的任何地方)将导致死锁,因为 c.Wait() 调用必须等待Broadcast() 永远不会出现,因为第一个 subscribe() 永远不会 return 直到 goroutineRunning.Done() 被调用(解除阻塞 goroutineRunning.Wait())。
将 goroutineRunning.Done() 移动到 c.Wait() 之前更好,但不会消除竞争。
要修复原始代码,您需要在调用 c.L.Lock() 之后放置 gorutineRunning.Done() 并锁定 Broadcast()。
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() {
c.L.Lock()
defer c.L.Unlock()
goroutineRunning.Done() // *** moved
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 dialog box!")
clickRegistered.Done()
})
subscribe(button.Clicked, func() {
fmt.Println("Mouse clicked.")
clickRegistered.Done()
})
button.Clicked.L.Lock() // *** new
button.Clicked.Broadcast()
button.Clicked.L.Unlock() // *** new
clickRegistered.Wait()
[顺便说一句,我承认这不是您最初问题的答案(其他答案应该得到分数),但我认为这值得一提。]