Ejemplo de sincronización.¿Grupo de espera correcto?


¿Es correcto este ejemplo de uso de sync.WaitGroup? Da el resultado esperado, pero no estoy seguro sobre el wg.Add(4) y la posición de wg.Done(). ¿Tiene sentido añadir las cuatro goroutinas a la vez con 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")
}

Resultado (como se esperaba):

Function in background, duration: 150ms
Function in background, duration: 200ms
Function in background, duration: 400ms
Function in background, duration: 600ms
Done
Author: topskip, 2013-10-06

3 answers

Sí, este ejemplo es correcto. Es importante que wg.Add() suceda antes de la declaración go para evitar condiciones de carrera. Lo siguiente también sería correcto:

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

Sin embargo, no tiene sentido llamar a wg.Add una y otra vez cuando ya sabes cuántas veces se llamará.


Waitgroups pánico si el contador cae por debajo de cero. El contador comienza en cero, cada Done() es un -1 y cada Add() depende del parámetro. Por lo tanto, para asegurarse de que el contador nunca caiga por debajo y evite el pánico, necesita que Add() esté garantizado para venir antes de Done().

En Go, tales garantías son dadas por el modelo de memoria .

El modelo de memoria establece que todas las sentencias en una sola goroutine parecen ejecutarse en el mismo orden en que se escriben. Es posible que en realidad no estén en ese orden, pero el resultado será como si lo fuera. También se garantiza que un goroutine no se ejecuta hasta después de la go declaración que lo llama. Dado que la Add() ocurre antes de la declaración go y la declaración go ocurre antes de la Done(), sabemos que la Add() ocurre antes de la Done().

Si tuviera que tener la instrucción go antes de la Add(), el programa podría funcionar correctamente. Sin embargo, sería una condición de carrera porque no estaría garantizada.

 115
Author: Stephen Weinberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-09 14:18:06

Recomendaría incrustar la llamada wg.Add() en la propia función doSomething(), de modo que si ajusta el número de veces que se llama, no tiene que ajustar por separado el parámetro add manualmente, lo que podría provocar un error si actualiza uno pero olvida actualizar el otro (en este ejemplo trivial, es poco probable, pero aún así, personalmente creo que es una mejor práctica para la reutilización de código).

Como Stephen Weinberg señala en su respuesta a esta pregunta , usted tiene que incrementa el waitgroup antes de generar el gofunc, pero puedes lograr esto fácilmente envolviendo el gofunc spawn dentro de la propia función doSomething(), así:

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

Entonces puedes llamarlo sin la invocación go, por ejemplo:

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

Como patio de recreo: http://play.golang.org/p/WZcprjpHa_

 18
Author: mroth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:03:04
  • pequeña mejora basada en la respuesta de Mroth
  • usar diferir para hecho es más seguro
  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")
}
 5
Author: Bnaya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-17 17:39:53