2016-04-26 206 views
10

我很難搞清楚如何正確使用sync.Cond。從我所知道的情況來看,在鎖定Locker和調用條件的Wait方法之間存在競爭條件。此示例將在主夠程的兩條線之間的人爲延遲,以模擬比賽條件:如何正確使用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

什麼我做錯了嗎?我如何避免這種明顯的競爭條件?有沒有更好的同步構造,我應該使用?


編輯:我知道我應該有更好的解釋,我想在這裏解決的問題。我有一個長時間運行的goroutine,它可以下載一個大文件和許多其他需要訪問HTTP標頭的goroutine。這個問題比聽起來更難。

我不能使用頻道,因爲只有一個goroutine會收到該值。還有一些其他的goroutines會試圖在標題已經可用之後很長時間檢索它。

下載程序goroutine可以簡單地將HTTP頭存儲在變量中,並使用互斥鎖來保護對它們的訪問。但是,這並不能爲其他goroutines「等待」它們變得可用提供一種方法。

我原本以爲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 
} 

這是如何工作的?

該互斥鎖在任務開始時被鎖定,確保任何呼叫WaitFor()將被阻止。一旦標題可用並且互斥量解鎖互斥鎖,則每次調用WaitFor()將一次執行一個。所有未來的調用(即使在goroutine結束後)都不會鎖定互斥鎖,因爲它總是被解鎖。

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

如果有什麼是不可能啓動夠程前鎖定互斥?例如,可能有其他的goroutine調用Wait()。 –

+0

比可能的情況是,當調用廣播時,不會通知其他goroutine。這也很好 - 但我們都沒有提到 - 通常情況與某些國家有關。等待意味着 - 當系統處於這種狀態時,我無法繼續等待。廣播的意思是 - 狀態改變了,每個等待的人都應該檢查他是否可以繼續。請更準確地描述兩個例程中計算的內容,以及爲什麼他們必須相互交流。 – lofcek

+0

對不起,我應該在原始問題中詳細介紹一下。我添加了一個編輯,描述了我正在嘗試做的事情。 –

1

看起來你c.Wait用於廣播其絕不會跟你的時間間隔發生。 隨着

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

您的片段似乎工作http://play.golang.org/p/OE8aP4i6gY。或者我失去了你嘗試才達到的東西嗎?

5

OP自己回答,但沒有直接回答原來的問題,我打算如何正確使用sync.Cond

如果每次寫入和讀取都有一個參數,那麼您並不需要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這裏只是用來等待goroutines完成它們的執行。

3

您需要確保c.Broadcast在您致電c.Wait之後撥打。你的程序的正確版本是:

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