web-dev-qa-db-ja.com

sync.Condを正しく使用するには?

sync.Cond を正しく使用する方法がわからない。私が知ることができることから、ロッカーをロックしてから条件のWaitメソッドを呼び出す間に競合状態が存在します。この例では、メインゴルーチンの2行間に人為的な遅延を追加して、競合状態をシミュレートします。

package main

import (
    "sync"
    "time"
)

func main() {
    m := sync.Mutex{}
    c := sync.NewCond(&m)
    go func() {
        time.Sleep(1 * time.Second)
        c.Broadcast()
    }()
    m.Lock()
    time.Sleep(2 * time.Second)
    c.Wait()
}

[ Go Playgroundで実行 ]

これにより、すぐにパニックが発生します。

致命的なエラー:すべてのゴルーチンがスリープ状態です-デッドロック! /src/runtime/sema.go:241 + 0x2e0 
 sync。(* Cond).Wait(0x10330200、0x0)
 /usr/local/go/src/sync/cond.go: 63 + 0xe0 
 main.main()
 /tmp/sandbox301865429/main.go:17 + 0x1a0

何が間違っていますか?この明らかな競合状態を回避するにはどうすればよいですか?使用すべきより良い同期構成はありますか?


Edit:ここで解決しようとしている問題をもっとよく説明したほうがいいと思います。大きなファイルをダウンロードする実行時間の長いゴルーチンと、HTTPヘッダーが利用可能なときにアクセスする必要がある他のゴルーチンがいくつかあります。この問題は思ったより難しいです。

1つのゴルーチンだけが値を受け取るため、チャネルを使用できません。そして、他のゴルーチンのいくつかは、ヘッダーが既に利用可能になってからずっと後にヘッダーを取得しようとしています。

ダウンローダーゴルーチンは、単にHTTPヘッダーを変数に保存し、ミューテックスを使用してそれらへのアクセスを保護できます。しかし、これは他のゴルーチンが利用可能になるのを「待つ」方法を提供しません。

私はsync.Mutexsync.Condの両方が一緒にこの目標を達成できると思っていましたが、これは不可能だと思われます。

22
Nathan Osman

OPは自分で答えましたが、元の質問には直接答えませんでした。正しく使用する方法を投稿しますsync.Cond

本当に必要ありませんsync.Cond書き込みと読み取りごとに1つのゴルーチンがある場合-単一のsync.Mutexは、それらの間の通信に十分です。 sync.Condは、複数のリーダーが共有リソースが利用可能になるのを待つ状況で役立ちます。

var sharedRsc = make(map[string]interface{})
func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    m := sync.Mutex{}
    c := sync.NewCond(&m)
    go func() {
        // this go routine wait for changes to the sharedRsc
        c.L.Lock()
        for len(sharedRsc) == 0 {
            c.Wait()
        }
        fmt.Println(sharedRsc["rsc1"])
        c.L.Unlock()
        wg.Done()
    }()

    go func() {
        // this go routine wait for changes to the sharedRsc
        c.L.Lock()
        for len(sharedRsc) == 0 {
            c.Wait()
        }
        fmt.Println(sharedRsc["rsc2"])
        c.L.Unlock()
        wg.Done()
    }()

    // this one writes changes to sharedRsc
    c.L.Lock()
    sharedRsc["rsc1"] = "foo"
    sharedRsc["rsc2"] = "bar"
    c.Broadcast()
    c.L.Unlock()
    wg.Wait()
}

プレイグラウンド

そうは言っても、状況が許せば、チャネルを使用することでデータを渡すことが推奨されます。

注意: sync.WaitGroupここでは、ゴルーチンが実行を完了するのを待つためにのみ使用されます。

17

C.Broadcastがc.Waitへの呼び出しの後にafter呼び出されることを確認する必要があります。プログラムの正しいバージョンは次のとおりです。

package main

import (
    "fmt"
    "sync"
)

func main() {
    m := &sync.Mutex{}
    c := sync.NewCond(m)
    m.Lock()
    go func() {
        m.Lock() // Wait for c.Wait()
        c.Broadcast()
        m.Unlock()
    }()
    c.Wait() // Unlocks m
}

https://play.golang.org/p/O1r8v8yW6h

8
eric chiang
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    m := sync.Mutex{}
    m.Lock() // main gouroutine is owner of lock
    c := sync.NewCond(&m)
    go func() {
        m.Lock() // obtain a lock
        defer m.Unlock()
        fmt.Println("3. goroutine is owner of lock")
        time.Sleep(2 * time.Second) // long computing - because you are the owner, you can change state variable(s)
        c.Broadcast()               // State has been changed, publish it to waiting goroutines
        fmt.Println("4. goroutine will release lock soon (deffered Unlock")
    }()
    fmt.Println("1. main goroutine is owner of lock")
    time.Sleep(1 * time.Second) // initialization
    fmt.Println("2. main goroutine is still lockek")
    c.Wait() // Wait temporarily release a mutex during wating and give opportunity to other goroutines to change the state.
    // Because you don't know, whether this is state, that you are waiting for, is usually called in loop.
    m.Unlock()
    fmt.Println("Done")
}

http://play.golang.org/p/fBBwoL7_pm

2
lofcek

C.Wait for Broadcastのように見えますが、これは時間間隔では発生しません。と

time.Sleep(3 * time.Second) //Broadcast after any Wait for it
c.Broadcast()

あなたのスニペットは動作しているようです http://play.golang.org/p/OE8aP4i6gY

1
Uvelichitel

はい。1つのチャネルを使用して、ヘッダーを複数のGoルーチンに渡すことができます。

headerChan := make(chan http.Header)

go func() { // This routine can be started many times
    header := <-headerChan  // Wait for header
    // Do things with the header
}()

// Feed the header to all waiting go routines
for more := true; more; {
    select {
    case headerChan <- r.Header:
    default: more = false
    }
}
0
Jonas