Como usar go routines

Que es un goroutine

Cuando trabajamos normalmente con go, las funciones se ejecutan de manera secuencial, lo que significa que la siguiente funcion no se ejecutara hasta que la enterior se haya terminado lo cual bloquea nuestra app por un momento.

Podemos verlo con el siguient codigo

package main

import (
	"fmt"
	"time"
)

func main() {
	begin := time.Now()

	fmt.Printf("Constructor sin go rutinas")

	Worker("Pablo", "Instalando ram")
	Worker("Nahuel", "Instalando procesador")

	fmt.Printf("Constructor termino en %.2f segundos", time.Since(begin).Seconds())
}

func Worker(name, job string) {

	fmt.Printf("El trabajador %s comenzo a trajar: %s\n", name, job)

	for i := 0; i < 10; i++ {
		fmt.Printf("El trabajador %s esta trabajando (%d/%d)\n", name, i+1, 10)

    //agregamos un tiempo de espera para que el resultado no sea siempre 0.0s
		time.Sleep(50 * time.Millisecond)

  }
}

Lo que nos devuelve en consola:

Constructor sin go rutinas
El trabajador Pablo comenzo a trajar: Instalando ram
El trabajador Pablo esta trabajando (1/10)
El trabajador Pablo esta trabajando (2/10)
El trabajador Pablo esta trabajando (3/10)
El trabajador Pablo esta trabajando (4/10)
El trabajador Pablo esta trabajando (5/10)
El trabajador Pablo esta trabajando (6/10)
El trabajador Pablo esta trabajando (7/10)
El trabajador Pablo esta trabajando (8/10)
El trabajador Pablo esta trabajando (9/10)
El trabajador Pablo esta trabajando (10/10)
El trabajador Nahuel comenzo a trajar: Instalando procesador
El trabajador Nahuel esta trabajando (1/10)
El trabajador Nahuel esta trabajando (2/10)
El trabajador Nahuel esta trabajando (3/10)
El trabajador Nahuel esta trabajando (4/10)
El trabajador Nahuel esta trabajando (5/10)
El trabajador Nahuel esta trabajando (6/10)
El trabajador Nahuel esta trabajando (7/10)
El trabajador Nahuel esta trabajando (8/10)
El trabajador Nahuel esta trabajando (9/10)
El trabajador Nahuel esta trabajando (10/10)
Constructor termino en 1.00 segundos%  

Este enfoque no es siempre el mas optimo.

Una goroutine o gorutina es una funcion o metodo que se ejecuta concurrentemente.

Es importante diferenciar entre concurrencia y paralelismo

Un programa que soporta paralelismo puede ejecutar dos o mas acciones de manera simultanea.

La concurrencia es la capacidad de que un proceso pueda hacer dos cosas a la vez, pero no en simultaneo. Por ejemplo, puedo cantar y tomar agua, pero no al mismo tiempo.

Las gorutinas son usada en patrones de disenio, como el patron worker pool

Como generar una goroutine

Para generar una goroutine tenemos que agregar la keyword go antes de la funcion.

package main

import (
	"fmt"
	"time"
)

func main() {
	begin := time.Now()

	fmt.Printf("Constructor sin go rutinas")

	go Worker("Pablo", "Instalando ram")
	go Worker("Nahuel", "Instalando procesador")

	fmt.Printf("Constructor termino en %.2f segundos", time.Since(begin).Seconds())
}

func Worker(name, job string) {

	fmt.Printf("El trabajador %s comenzo a trajar: %s\n", name, job)

	for i := 0; i < 10; i++ {
		fmt.Printf("El trabajador %s esta trabajando (%d/%d)\n", name, i+1, 10)

    //agregamos un tiempo de espera para que el resultado no sea siempre 0.0s
		time.Sleep(50 * time.Millisecond)

  }
}

Ahora la gorutina no bloquea el resto de la ejecucion de nuestro programa.

En este caso, las goroutine no llegan a ejecutarse porque el programa termina antes.

Si vemos la consola de salida veremos esto:

constructor sin gorutinas termino en 0.0 segundos

Entonces te estaras preguntando, como hago para que mi gorutina se ejecute?, ni se te ocurra usar time.Sleep() para dar tiempo a estas gorutinas. Para esto tenemos WaitGroup.

WaitGroup detendra la ejecucion del programa pero esperara a que todas nuestras goroutines terminen.

internamente WaitGroup funciona con un contador, cuando este contado sea mayor a cero, va a esperar a que se terminen de ejecutar las rutinas.

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	begin := time.Now()

	fmt.Printf("Constructor sin go rutinas")

+	wg := &sync.WaitGroup{}

+	//Add inicializa el contador que se ira disminuyendo
+	wg.Add(2)
+	go Worker("Pablo", "Instalando ram", wg)
+	go Worker("Nahuel", "Instalando procesador", wg)

+	wg.Wait()
	fmt.Printf("Constructor termino en %.2f segundos", time.Since(begin).Seconds())
}

+func Worker(name, job string, wg *sync.WaitGroup) {

	fmt.Printf("El trabajador %s comenzo a trajar: %s\n", name, job)

	for i := 0; i < 10; i++ {
		fmt.Printf("El trabajador %s esta trabajando (%d/%d)\n", name, i+1, 10)
		time.Sleep(50 * time.Millisecond)
	}

+	//Done se encarga de disminuir el contador
+	defer wg.Done()

}

> Tip: usa defer sobre el método Done para garantizar que sea lo último que se ejecute.

El método Add

El método Add incrementa el contador del WaitGroup en n unidades, donde n es el argumento que le pasamos.

El Método Done

El método Done se encarga de disminuir una unidad del contador del WaitGroup. Lo llamaremos para avisarle al WaitGroup que la goroutine ha finalizado y decremente el contador en uno.

Funciones anónimas en goroutines

Cuando se usan gorutinas, es bastante común utilizar funciones anónimas para evitar declarar una función nueva.

go func() {
}()

Recuerda que los paréntesis que aparecen tras el cuerpo de la función ejecutan la función anónima que declaramos y también reciben sus argumentos.

go func(text string) {
}("Texto")

Como saber si una goroutine esta usando concurrencia o paralelismo?

Nosotros desde el codigo solo podemos decir que funciones trabajaran de manera asincrona.Go se encarga por si mismo de determinar si usara concurrencia o paralelismo en runtime