Cet exemple d'utilisation de sync.WaitGroup
Est-il correct? Cela donne le résultat attendu, mais je ne suis pas sûr de la fonction wg.Add(4)
ni de la position de wg.Done()
. Est-il judicieux d’ajouter les quatre goroutines en même temps avec wg.Add()
?
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")
}
Résultat (comme prévu):
Function in background, duration: 150ms
Function in background, duration: 200ms
Function in background, duration: 400ms
Function in background, duration: 600ms
Done
Oui, cet exemple est correct. Il est important que la fonction wg.Add()
se produise avant l'instruction go
afin d'éviter les situations de concurrence critique. Ce qui suit serait également correct:
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")
}
Cependant, il est plutôt inutile d'appeler wg.Add
Encore et encore quand vous savez déjà combien de fois il sera appelé.
Waitgroups
panique si le compteur tombe en dessous de zéro. Le compteur commence à zéro, chaque Done()
est un -1
Et chaque Add()
dépend du paramètre. Donc, pour que le compteur ne descende jamais en dessous et éviter les paniques, vous avez besoin que la Add()
soit garantie avant la Done()
.
En Go, de telles garanties sont données par le modèle de mémoire .
Le modèle de mémoire indique que toutes les instructions d'une même goroutine semblent être exécutées dans le même ordre d'écriture. Il est possible qu'ils ne soient pas dans cet ordre, mais le résultat sera comme si c'était le cas. Il est également garanti qu'un goroutine ne s'exécute pas après l'instruction go
qui l'appelle . Comme la Add()
survient avant l'instruction go
et que l'instruction go
se produise avant la Done()
, nous savons que la Add()
se produit avant la Done()
.
Si vous deviez avoir l'instruction go
avant la Add()
, le programme peut fonctionner correctement. Cependant, ce serait une condition de concurrence parce que cela ne serait pas garanti.
Je recommanderais d'intégrer l'appel wg.Add()
dans la fonction doSomething()
elle-même, de sorte que si vous réglez le nombre de fois qu'il est appelé, vous ne devez pas régler séparément le paramètre d'ajout manuellement. pourrait conduire à une erreur si vous mettez à jour l’un en oubliant de mettre à jour l’autre (dans cet exemple trivial, il est peu probable, mais je pense personnellement que c’est une meilleure pratique pour la réutilisation du code).
Comme Stephen Weinberg le fait remarquer dans sa réponse à cette question , vous devez incrémenter le groupe d’attente avant pour engendrer le gofunc, mais vous pouvez accomplir cela facilement en encapsulant le spawn gofunc dans la fonction doSomething()
elle-même, comme ceci:
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()
}()
}
Ensuite, vous pouvez l’appeler sans l’invocation go
, par exemple:
func main() {
var wg sync.WaitGroup
dosomething(200, &wg)
dosomething(400, &wg)
dosomething(150, &wg)
dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
En tant que terrain de jeu: http://play.golang.org/p/WZcprjpHa_
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) { wg.Add(1) go func() { defer wg.Done() duration := millisecs * time.Millisecond time.Sleep(duration) fmt.Println("Function in background, duration:", duration) }() } func main() { var wg sync.WaitGroup dosomething(200, &wg) dosomething(400, &wg) dosomething(150, &wg) dosomething(600, &wg) wg.Wait() fmt.Println("Done") }