Las rutinas Go son un excelente punto de venta para Golang, por lo que es una elección de muchos desarrolladores. En este post veremos un problema común con estos e intentaremos solucionarlo.
Veamos un fragmento de código simple que ilustra este problema,
Go
package main import "fmt" func runner1() { fmt.Print("\nI am first runner") } func runner2() { fmt.Print("\nI am second runner") } func execute() { go runner1() go runner2() } func main() { // Launching both the runners execute() }
Como acaba de ver, no había nada en la salida, esto se debe a que tan pronto como inicia ambas rutinas, su función principal acaba de terminar. Y cada programa en Golang se ejecuta hasta que la función principal no finaliza. Entonces, ¿qué podemos hacer con este problema?
1. Podemos esperar un tiempo después de iniciar los corredores, para este propósito usaremos la función de paquetes de » tiempo » » Sueño » que detiene la ejecución de la función por una duración determinada,
Go
package main import ( "fmt" "time" ) func runner1() { fmt.Print("\nI am first runner") } func runner2() { fmt.Print("\nI am second runner") } func execute() { go runner1() go runner2() } func main() { // Launching both the runners execute() time.Sleep(time.Second) }
Producción:
I am second runner I am first runner
Simplemente solucionamos el problema, después de iniciar nuestros corredores esperamos un segundo, por lo que nuestra función principal estuvo durmiendo (bloqueada) durante 1 segundo. En esa duración, todas las rutinas go se ejecutaron con éxito. Pero Golang es un lenguaje rápido, no toma 1 segundo imprimir solo 2 strings.
El problema es que nuestros ejecutores se ejecutan en poco tiempo, por lo que bloqueamos innecesariamente el programa durante 1 segundo. En este ejemplo, no parece ser un problema crítico, pero si crea un servidor de grado de producción que atenderá miles de requests al mismo tiempo, será un gran problema.
2. Usemos otra primitiva de biblioteca estándar de Golang “ sync.WaitGroup ”. WaitGroup es en realidad un tipo de contador que bloquea la ejecución de la función (o podría decir una gorutina) hasta que su contador interno se convierte en 0 .
Cómo funciona ?
WaitGroup exporta 3 métodos.
1 | Agregar (int) | Aumenta el contador de grupo de espera en un valor entero dado. |
2 | Hecho() | Disminuye el contador de WaitGroup en 1, lo usaremos para indicar la finalización de una gorutina. |
3 | Esperar() | Bloquea la ejecución hasta que su contador interno pasa a 0. |
Nota : WaitGroup es seguro para la concurrencia, por lo que es seguro pasarle un puntero como argumento para Groutines.
Go
package main import ( "fmt" "sync" ) func runner1(wg *sync.WaitGroup) { defer wg.Done() // This decreases counter by 1 fmt.Print("\nI am first runner") } func runner2(wg *sync.WaitGroup) { defer wg.Done() fmt.Print("\nI am second runner") } func execute() { wg := new(sync.WaitGroup) wg.Add(2) // We are increasing the counter by 2 // because we have 2 goroutines go runner1(wg) go runner2(wg) // This Blocks the execution // until its counter become 0 wg.Wait() } func main() { // Launching both the runners execute() }
Producción:
I am second runner I am first runner
La salida es la misma, pero nuestro programa no se bloquea durante 1 segundo. El patrón que le mostramos anteriormente es una práctica común al escribir código concurrente en Golang.