2013-10-06 10 views
60

Ist diese Beispielverwendung von sync.WaitGroup korrekt? Es gibt das erwartete Ergebnis, aber ich bin unsicher über die und die Position wg.Done(). Macht es Sinn, die vier Goroutines gleichzeitig mit wg.Add() hinzuzufügen?Beispiel für sync.WaitGroup korrekt?

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

Ergebnis (wie erwartet):

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

Was passiert, wenn dosomething() stürzt ab, bevor es wg.Done tun kann()? –

+4

Ich weiß, dass das alt ist, aber für zukünftige Leute würde ich einen anfänglichen 'defer wg.Done()' Aufruf am Anfang der Funktion empfehlen. –

Antwort

85

Ja, das Beispiel korrekt ist. Es ist wichtig, dass der wg.Add() vor der go Anweisung auftritt, um Race-Bedingungen zu verhindern. In der folgenden wäre auch richtig sein:

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

Es ist jedoch ziemlich sinnlos ist wg.Add über anrufen und immer wieder, wenn Sie bereits wissen, wie oft sie aufgerufen werden.


Waitgroups Panik, wenn der Zähler unter Null fällt. Der Zähler beginnt bei Null, jeder Done() ist ein -1 und jeder Add() hängt von dem Parameter ab. Also, Sie brauchen die Add() zu garantiert zu kommen, bevor die Done() Panik zu vermeiden.

In Go werden solche Garantien durch die memory model gegeben.

Das Speichermodell besagt, dass alle Anweisungen in einer einzelnen Goroutine scheinbar in der gleichen Reihenfolge ausgeführt werden, in der sie geschrieben wurden. Es ist möglich, dass sie nicht wirklich in dieser Reihenfolge sind, aber das Ergebnis wird so sein, als ob es wäre. Es ist auch gewährleistet, dass ein goroutine doesn't run until after the go statement that calls it. Da die Add() vor der go Anweisung auftritt und die go Anweisung vor der Done() auftritt, wissen wir, dass die Add() vor der Done() auftritt.

Wenn Sie die go Anweisung vor die Add() kommen, könnte das Programm korrekt funktionieren. Es wäre jedoch eine Race Condition, da dies nicht garantiert wäre.

+7

Ich habe eine Frage zu diesem Thema: wäre es nicht besser, "wg.Done()" zu verschieben, so dass wir sicher sind, dass es unabhängig von der Route aufgerufen wird, die die Goroutine einnimmt? Vielen Dank. –

+2

Wenn Sie nur sicherstellen wollten, dass die Funktion nicht zurückkehrt, bevor alle Routinen fertiggestellt sind, dann wäre eine Verschiebung vorzuziehen. Normalerweise ist der ganze Punkt einer Wartegruppe, zu warten, bis die ganze Arbeit erledigt ist, um dann etwas mit den Ergebnissen zu tun, auf die Sie gewartet haben. – Zanven

12

Ich würde empfehlen, die wg.Add() Anruf in die doSomething() Funktion selbst, so dass, wenn Sie die Anzahl der Zeiten, die Sie aufgerufen haben, müssen Sie nicht separat anpassen add Parameter manuell, die zu einem Fehler führen könnte, wenn Sie aktualisieren man vergisst aber, das andere zu aktualisieren (in diesem trivialen Beispiel ist das unwahrscheinlich, aber ich persönlich glaube, dass es besser für die Code-Wiederverwendung ist).

Wie Stephen Weinberg in his answer to this question weist darauf hin, Sie die gofunc die waitgroup erhöhen müssen vor zu Laichen, aber Sie können dies durch Wickeln des gofunc Laich in der doSomething() Funktion selbst, wie dies leicht erreichen:

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

Dann können Sie es ohne den go Aufruf aufrufen, z:

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

Als Spielplatz: http://play.golang.org/p/WZcprjpHa_