web-dev-qa-db-fra.com

Exemple pour sync.WaitGroup correct?

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
98
topskip

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.

143
Stephen Weinberg

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_

26
mroth
  • petite amélioration basée sur la réponse de Mroth
  • utiliser reporter pour Fait est plus sûr
  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")
}
13
Bnaya