2013-10-06 27 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_