2016-04-26 3 views
10

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

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() 
} 

[Run on the Go Playground]

をこれはすぐにパニックが発生します。

fatal error: all goroutines are asleep - deadlock! 

goroutine 1 [semacquire]: 
sync.runtime_Syncsemacquire(0x10330208, 0x1) 
    /usr/local/go/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

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


編集:私はより良い私はここで解決しようとしている問題を説明している必要があります実現。私は長いファイルをダウンロードし、利用可能なときにHTTPヘッダーにアクセスする必要がある他のいくつかのgoroutineをダウンロードする長年のgoroutineを持っています。この問題はそれよりも難しいです。

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

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

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

答えて

1

私はついにこれを行う方法を発見しました。sync.Condはまったく含まれていません - ミューテックスだけです。

type Task struct { 
    m  sync.Mutex 
    headers http.Header 
} 

func NewTask() *Task { 
    t := &Task{} 
    t.m.Lock() 
    go func() { 
     defer t.m.Unlock() 
     // ...do stuff... 
    }() 
    return t 
} 

func (t *Task) WaitFor() http.Header { 
    t.m.Lock() 
    defer t.m.Unlock() 
    return t.headers 
} 

これはどのように機能しますか?

mutexはタスクの開始時にロックされ、WaitFor()を呼び出す何らかのものがブロックされることを保証します。ヘッダーが利用可能になり、ゴルーチンによってロックアウトされたmutexが呼び出されると、WaitFor()の各呼び出しは一度に1つずつ実行されます。すべての将来の呼び出し(ゴルーチンが終了した後でさえ)は常にロックされていないので、mutexのロックに問題はありません。

2
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

+0

ことができない場合ゴルーチンを起動する前に、ミューテックスをロックしますか?たとえば、Wait()を呼び出す他のゴルーチンがあるかもしれません。 –

+0

Broadcastが呼び出されたときに他のゴルーチンが通知されることはありません。それはうまくいく - しかし、私たちはどちらも言及していない - 通常状態はいくつかの状態に関連しています。そして、待つこと - 私はこの状態にシステムがある間、私は続けることができません。ブロードキャストは状態が変わったことを意味します。待っていた人は誰でも続けることができるかどうかを確認する必要があります。両方のgoroutineで計算されるものと、なぜ彼らが互いに通信しなければならないのかをより正確に記述してください。 – lofcek

+0

申し訳ありませんが、私は元の質問でさらに詳しく説明する必要があります。私は何をしようとしているのかを正確に記述した編集を追加しました。 –

1

自分の時間間隔で発生することはないブロードキャストのためのあなたc.Waitように見えます。

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

スニペットで は.ORは、私はあなたがachiveしようとする何かをしないのですhttp://play.golang.org/p/OE8aP4i6gYを動作するようですか?

5

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

書き込みと読み取りごとに1つのゴルーチンがある場合は、実際にはsync.Condは必要ありません。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() 
} 

Playground

チャンネルを使用すると、まだ状況が許すならば周りにデータを渡すために推奨される方法である、と述べました。

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

3

の呼び出し後にc.Broadcastがとなっていることを確認する必要があります。あなたのプログラムの正しいバージョンは次のようになります。

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

関連する問題