对goroutine的延期感到困惑

问题描述 投票:0回答:1

我遇到了以下代码段,它们演示了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

有人可以引导我理解为什么添加延迟会导致代码崩溃吗?

go synchronization deadlock goroutine
1个回答
0
投票

原始代码在goroutine开始运行后立即释放等待组。当subscribe函数返回时,goroutine仍处于活动状态。

[将其更改为defer goroutineRunning.Done()时,goroutine开始,在c.Wait()处停止,因为它正在等待条件变量的广播。由于goroutine正在等待,因此不会调用goroutineRunning.Donesubscribe功能会在goroutineRunning.Wait处停止。因此,首次调用订阅时,它将创建一个等待cond的goroutine,订阅本身将开始在waitgroup中等待。有goroutines(主要程序和一个由subscribe开头的程序),都等待某个事件发生,但是没有其他goroutines运行以使该事件发生,所以出现了死锁。

© www.soinside.com 2019 - 2024. All rights reserved.