Смущенный откладывать в goroutines - PullRequest
1 голос
/ 04 февраля 2020

Я наткнулся на следующий фрагмент кода, демонстрирующий функциональность «широковещания» в syn c .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!

Я изменил подпрограмму в подписке, чтобы отложить вызов Done для группы ожидания gorroutineRunning после Горутина выполнена, выполняя. Я думал, что группа ожидания должна уменьшаться только после выполнения процедуры 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()
    }

    ....
}

С добавлением отсрочки я получаю следующую пани c:

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

Может кто-нибудь объяснить мне, почему при добавлении отсрочки вызывает код для pani c?

Ответы [ 2 ]

1 голос
/ 04 февраля 2020

Я думаю, что вы правильно определили, что с goroutineRunning WaitGroup что-то не так.

Я думаю, что оригинальный код не включал WaitGroup, но тот, кто написал код, обнаружил, что произошла гонка. условие, когда Broadcast () вызывался (для переменной syn c .Cond) до того, как 3 go -программы вызвали Wait (). Использование goroutineRunning было попыткой исправить это, но оно не исключает условия гонки, а лишь снижает вероятность этого. Например, если вы перешли в режим сна после goroutineRunning.Done (), то у вас возникнет та же проблема - Broadcast () вызывается до того, как 3 go -программы ожидают.

Чтобы вернуться к исходному вопросу ... Перемещение goroutine.Done () вперед (в любом месте после вызова Wait) вызовет взаимоблокировку, так как вызов c .Wait () должен ожидать Broadcast (), который никогда не может прийти, поскольку первая подписка () будет никогда не возвращаться, пока не будет вызван goroutineRunning.Done () (чтобы разблокировать goroutineRunning.Wait ()).

Перемещение goroutineRunning.Done () до тех пор, пока c .Wait () не станет лучше, но не устранит гонку .

Чтобы исправить исходный код, вам нужно поставить gorutineRunning.Done () после вызова c .L.Lock (), а также заблокировать 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()

[Кстати, я принимаю, что это не ответ на ваш первоначальный вопрос (и что другой ответ должен получить очки), но я подумал, что стоит упомянуть.]

1 голос
/ 04 февраля 2020

Исходный код освобождает группу ожидания, как только запускается программа. Когда возвращается функция subscribe, программа активна.

Когда вы изменили это значение на defer goroutineRunning.Done(), программа запускается и останавливается на c.Wait(), потому что она ожидает трансляции переменной условия. Поскольку программа ожидает там, goroutineRunning.Done не вызывается, а функции subscribe останавливаются на goroutineRunning.Wait. Поэтому, когда вы в первый раз вызываете подписку, он создает программу, ожидающую на cond, сама подписка начинает ждать в группе ожидания. Есть функции (основная и та, которая была запущена по подписке), ожидающие какого-либо события, но другие программы не выполняются, чтобы это событие произошло, поэтому тупик.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...