En la primera parte sobre concurrencia hablamos sobre cómo empezar a realizar concurrencia, y de algunas de las particularidades nativas que nos traía Go, como es la palabra go para lanzar las gorrutinas, y dentro del mundo de los canales, los unbuffered channels
y como gestionarlos con select
y bucles infinitos dentro de otras gorrutinas.
Por si todo esto fuera poco, nuestro compañero Joan, nos hablaba del modelo de actores, algo muy común en lenguajes como Elixir o Erlang, y cómo realizarlo en Go.
Hoy vamos a tratar los WaitGroups, el cual nos permitirá sincronizar gorrutinas de una manera realmente sencilla y útil.
WaitGroups
Si recordamos de nuestros primeros pasos con gorrutinas, necesitábamos en los ejemplos más básicos necesitábamos utilizar un time.Sleep
para poder sincronizar nuestras rutinas, pero esto no nos aseguraba que las mismas acabaran sincronizadas, era un poco a ojo.
¿Qué es esto de sincronizar rutinas? Os estaréis preguntando, vayamos por partes, algo importante que no dijimos al principio es que todo programa Go se ejecuta dentro de una gorrutina, así que si lanzamos una nueva gorrutina necesitaríamos sincronizarla con la gorrutina principal, siempre y cuando lo necesitemos claro está.
Sincronizar rutinas consiste en palabras llanas, que suceda todo aquello que queremos que suceda, como sabemos las rutinas se ejecutan en diferentes hilos con lo cual deberemos de comunicarlas entre ellas para saber cuando terminan, actualizar estados etc.
Vamos a ver un ejemplo muy sencillo de lo que queremos decir.
package main
import (
"fmt"
)
func main() {
go func() {
fmt.Println("I'm a gorutine")
}()
fmt.Println("Hello, playground")
}
Si ejecutamos el siguiente código, veremos que realmente el mensaje I'm a gorutine
nunca llega a ejecutarse, podríamos poner un time.Sleep
para esperarnos, pero claro esto en ejemplos más complejos no nos hará el apaño, ¿cómo lo solucionamos?
Podemos solucionarlo con canales como vimos anteriormente:
package main
import (
"fmt"
)
func main() {
done := make(chan struct{})
go func() {
fmt.Println("I'm a gorutine")
done <- struct{}{}
}()
<-done
fmt.Println("Hello, playground")
}
Pero Go nos ofrece otra manera, los WaitGroups, dentro del paquete sync
el cuál iremos conociendo en el blog poco a poco en futuros artículos, podemos encontrar los WaitGroups
. Gracias a los WaitGroups
podemos encargarnos de las situaciones donde necesitemos realizar procesos concurrentemente, pero esperarnos a que terminen, como es el caso anterior.
Vamos a ver como queda el ejemplo anterior utilizando los WaitGroups
package main
import (
"fmt"
"sync"
)
func main() {
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
fmt.Println("I'm a gorutine")
wg.Done()
}()
wg.Wait()
fmt.Println("Hello, playground")
}
Vamos por partes, lo primero que hemos hecho ha sido inicializar nuestro WaitGroup
, como cualquier otro struct, después gracias al método Add
le decimos cuantas rutinas vamos a ejecutar, o más bien cuantas veces llamaremos al Done()
antes de salir, finalmente gracias al Wait()
bloquearemos la ejecución hasta que recibamos los Done()
que habíamos indicado anteriormente.
Hay que además hacer un apunte, si nuestro WaitGroup
se pasa a una función o método éste deberá ser pasado por referencia, de lo contrario, estaríamos tratando sobre una copia y nunca recibiríamos el Done()
pertinente.
Vamos a aclararlo con un simple ejemplo:
package main
import (
"fmt"
"sync"
)
func main() {
wg := sync.WaitGroup{}
wg.Add(1)
go printSomething(wg)
wg.Wait()
fmt.Println("Hello, playground")
}
func printSomething(wg sync.WaitGroup) {
fmt.Println("I'm a gorutine")
wg.Done()
}
Como véis hemos obtenido un maravilloso deadlock
y eso se debe a que estaremos esperando eternamente en el wg.Wait()
, para arreglarlo bastaría por pasarlo por referencia:
package main
import (
"fmt"
"sync"
)
func main() {
wg := sync.WaitGroup{}
wg.Add(1)
go printSomething(&wg)
wg.Wait()
fmt.Println("Hello, playground")
}
func printSomething(wg *sync.WaitGroup) {
fmt.Println("I'm a gorutine")
wg.Done()
}
Conclusión
En este tutorial, hemos aprendido todo lo básico sobre los WaitGroups
, que son y cómo utilizarlos, viendo que son realmente sencillos y poderosos de utilizar, ya que nos da un control muy rápido de nuestras gorrutinas.
Recordamos que si queréis dejarnos cualquier comentario o sugerencia lo podéis hacer a través de Twitter, en @FriendsofGoTech o en los propios comentarios del blog, ¡estaremos encantados de recibir vuestras dudas o comentarios!