115 votes

Veuillez traduire ceci en français en conservant les mêmes balises HTML s'il y a lieu : Exemple pour sync.WaitGroup correct?

Est-ce que cet exemple d'utilisation de sync.WaitGroup est correct ? Il donne le résultat attendu, mais je ne suis pas sûr de wg.Add(4) et de la position de wg.Done(). Est-ce logique d'ajouter les quatre goroutines en une seule fois 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("Fonction en arrière-plan, durée:", 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("Fait")
}

Résultat (comme prévu) :

Fonction en arrière-plan, durée: 150ms
Fonction en arrière-plan, durée: 200ms
Fonction en arrière-plan, durée: 400ms
Fonction en arrière-plan, durée: 600ms
Fait

157voto

Stephen Weinberg Points 12682

Oui, cet exemple est correct. Il est important que le wg.Add() se produise avant l'instruction go pour éviter les conditions de concurrence. 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("Fait")
}

Cependant, il est plutôt inutile d'appeler wg.Add encore et encore quand on sait déjà combien de fois il sera appelé.


Les Waitgroups lancent une panique si le compteur tombe en dessous de zéro. Le compteur démarre à zéro, chaque Done() est un -1 et chaque Add() dépend du paramètre. Ainsi, pour garantir que le compteur ne descend jamais en dessous et éviter les paniques, vous avez besoin que le Add() soit garanti de venir avant le Done().

En Go, de telles garanties sont fournies par le modèle de mémoire.

Le modèle mémoire stipule que toutes les déclarations dans une seule goroutine semblent être exécutées dans le même ordre que celui dans lequel elles sont écrites. Il est possible qu'elles ne soient pas réellement dans cet ordre, mais le résultat sera comme s'il l'était. Il est également garanti qu'une goroutine ne s'exécutera pas avant l'instruction go qui l'appelle. Étant donné que le Add() se produit avant l'instruction go et que l'instruction go se produit avant le Done(), nous savons que le Add() se produit avant le Done().

Si vous aviez l'instruction go avant le Add(), le programme pourrait fonctionner correctement. Cependant, ce serait une condition de concurrence car ce ne serait pas garanti.

30voto

mroth Points 457

Je recommande d'intégrer l'appel à wg.Add() dans la fonction doSomething() elle-même, de sorte que si vous ajustez le nombre de fois où il est appelé, vous n'aurez pas à ajuster manuellement le paramètre d'ajout séparément, ce qui pourrait entraîner une erreur si vous en mettez à jour un mais oubliez de mettre à jour l'autre (dans cet exemple trivial, c'est peu probable, mais néanmoins, je pense personnellement qu'il est préférable pour la réutilisation du code).

Comme le souligne Stephen Weinberg dans sa réponse à cette question, vous devez incrémenter le waitgroup avant d'appeler la fonction gofunc, mais vous pouvez facilement accomplir cela en enveloppant le spawn de gofunc à l'intérieur de 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("Fonction en arrière-plan, durée :", duration)
        wg.Done()
    }()
}

Ensuite, vous pouvez l'appeler sans l'invocation de 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("Terminé")
}

Comme un terrain de jeu : http://play.golang.org/p/WZcprjpHa_

24voto

Bnaya Points 417
  • petite amélioration basée sur la réponse de Mroth
  • utiliser defer pour Done est plus sûr
  func dosomething(millisecond time.Duration, wg *sync.WaitGroup) {
  wg.Add(1)
  go func() {
      defer wg.Done()
      duration := millisecond * time.Millisecond
      time.Sleep(duration)
      fmt.Println("Fonction en arrière-plan, durée:", duration)
  }()
}

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X