2013-10-06 23 views
60

sync.WaitGroup是否正確嗎?它給出了預期的結果,但我不確定wg.Add(4)wg.Done()的位置。與wg.Add()一起添加四個套餐是否有意義?sync.WaitGroup示例正確嗎?

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

package main 

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

func dosomething(millisecs time.Duration, wg *sync.WaitGroup) { 
    duration := millisecs * time.Millisecond 
    time.Sleep(duration) 
    fmt.Println("Function in background, duration:", duration) 
    wg.Done() 
} 

func main() { 
    var wg sync.WaitGroup 
    wg.Add(4) 
    go dosomething(200, &wg) 
    go dosomething(400, &wg) 
    go dosomething(150, &wg) 
    go dosomething(600, &wg) 

    wg.Wait() 
    fmt.Println("Done") 
} 

結果(如預期):

Function in background, duration: 150ms 
Function in background, duration: 200ms 
Function in background, duration: 400ms 
Function in background, duration: 600ms 
Done 
+0

如果什麼DoSomething的()崩潰之前它可以做wg.Done()? –

+4

我意識到這是舊的,但對於未來的人,我會建議在函數啓動時初始化「推遲wg.Done()」調用。 –

回答

85

是的,本實施例中是正確的。在go聲明之前發生wg.Add()以防止競爭條件是很重要的。下面也將是正確的:

func main() { 
    var wg sync.WaitGroup 
    wg.Add(1) 
    go dosomething(200, &wg) 
    wg.Add(1) 
    go dosomething(400, &wg) 
    wg.Add(1) 
    go dosomething(150, &wg) 
    wg.Add(1) 
    go dosomething(600, &wg) 

    wg.Wait() 
    fmt.Println("Done") 
} 

然而,這是相當無意義的一遍一遍打電話wg.Add當你已經知道多少次被調用。


Waitgroups恐慌,如果計數器降到零度以下。計數器從0開始,每個Done()-1和各Add()取決於參數。所以,你需要Add()保證來的Done()之前,以避免恐慌。

在圍棋,這種擔保是由memory model給出。

的內存模型指出,在一個單一的goroutine所有語句出現在相同的順序執行,因爲他們寫的。他們可能不會按照這個順序,但結果就好像是這樣。它也保證了goroutine doesn't run until after the go statement that calls it。由於Add()go語句之前發生,並且Done()之前發生go聲明,我們知道Add()Done()之前發生。

如果你有go語句來的Add()之前,該程序可以正常運行。但是,這將是一個競爭條件,因爲它不會得到保證。

+7

我對這個問題有疑問:推遲wg.Done()不是更好嗎?這樣我們可以確定它會被調用,而不管goroutine所採用的路由如何?謝謝。 –

+2

如果你完全想確保在所有的例行程序完成之前函數沒有返回,那麼yes延遲將是首選。通常一個等待小組的整個要點就是等待所有的工作完成,然後對你等待的結果進行一些處理。 – Zanven

12

我建議將wg.Add()調用嵌入到doSomething()函數本身中,以便如果調整調用的次數,則無需手動單獨調整add參數,如果更新可能會導致錯誤一但忘了更新其他(在這個不太可能的小例子中,但我仍然認爲這是更好的代碼重用實踐)。

正如斯蒂芬·溫伯格在his answer to this question指出,你必須在waitgroup 之前遞增到產卵gofunc,但你可以通過包裝gofunc產卵的doSomething()函數本身內部,這樣做到這一點很容易:

func dosomething(millisecs time.Duration, wg *sync.WaitGroup) { 
    wg.Add(1) 
    go func() { 
     duration := millisecs * time.Millisecond 
     time.Sleep(duration) 
     fmt.Println("Function in background, duration:", duration) 
     wg.Done() 
    }() 
} 

然後你可以在不調用go的情況下調用它,例如:

func main() { 
    var wg sync.WaitGroup 
    dosomething(200, &wg) 
    dosomething(400, &wg) 
    dosomething(150, &wg) 
    dosomething(600, &wg) 
    wg.Wait() 
    fmt.Println("Done") 
} 

作爲一個遊樂場:http://play.golang.org/p/WZcprjpHa_