golang go goroutine

golang - Ejemplo para sync.WaitGroup ¿correcto?



golang channel (2)

¿Este ejemplo de uso de sync.WaitGroup correcto? Da el resultado esperado, pero no estoy seguro del wg.Add(4) y la posición de wg.Done() . ¿Tiene sentido agregar los cuatro goroutines 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


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

Como señala Stephen Weinberg en su respuesta a esta pregunta , tienes que incrementar el grupo de espera antes de generar el gofunc, pero puedes lograrlo fácilmente envolviendo el spawn gofunc dentro de la 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() }() }

Luego puede 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 campo de juego: http://play.golang.org/p/WZcprjpHa_


Sí, este ejemplo es correcto. Es importante que wg.Add() ocurra antes de la instrucció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, es inútil llamar wg.Add una y otra vez cuando ya sabe cuántas veces se llamará.

Waitgroups entran en 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 garantizar que el contador nunca caiga por debajo y evitar los pánicos, necesita que Add() se garantice antes del Done() .

En Go, tales garantías están dadas por el modelo de memoria .

El modelo de memoria indica que todas las instrucciones en una sola rutina 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 una rutina no se ejecutará hasta después de la instrucción go que la llame . Como Add() ocurre antes de la instrucción go y la instrucción go ocurre antes de Done() , sabemos que Add() ocurre antes de Done() .

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