Herramientas para manejar Rutinas de Go
Tags: go, concurrency
Published: 2023-05-12 03:31:44

## Introducción
Como expliqué en algún post anterior, Golang ofrece herramientas muy interesantes y útiles, que nos ayudan a desarrollar nuestras aplicaciones. Por otro lado, también mencioné que Golang ofrece muchas facilidades para manejo de concurrencia, acá veremos algunas de esas herramientas que harán de nuestro trabajo concurrente más robusto.

## Herramientas
En este post vamos a mencionar solo 3 de las muchas herramientas que ofrece Go para el manejo de concurrencia, sin entrar demasiado en detalle, próximamente planeo adentrarme un poco más en estos.

### Rutinas de Go
Primero para entender cómo son y cómo funcionan las rutinas en golang, escribí sobre esto en un [post anterior](https://dev.to/rlgino/rutinas-en-golang-1869).

### Canales
Partiendo de lo que son rutinas (threads virtuales livianos) los canales son, como su nombre lo índica, canales de comunicación entre estas rutinas, es una herramienta poderosa, porque cuando empezamos a ejecutar nuestras rutinas en paralelo, a veces nos encontraremos con la necesidad de compartir recursos entre estas rutinas.
Ante todo el slogan de la concurrencia en Golang es "No comunicarse compartiendo la memoria, compartir la memoria al comunicarse."
Habiendo dicho esto, podemos partir del siguiente ejemplo que ilustra el funcionamiento de un canal en Go:

```go
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
    messages := make(chan string)

    // Acá se ejecuta la rutina y se pasa el canal
    go sendString(messages)

    // Acá se recibe el valor que se envío por el canal
    // Y se guarda en la variable msg
    msg := <-messages
    // Mientras esperamos recibir algo en el canal `messages` el thread queda bloqueado
    fmt.Printf("Message received %s", msg)
    fmt.Println("Another actions...")
}

func sendString(msg chan string) {
    text := readFromConsole()
    // Dentro del canal, enviamos el valor por el canal
    // Y seguimos el hilo de nuestra rutina
    msg <- text
}

func readFromConsole() string {
    reader := bufio.NewReader(os.Stdin)
    fmt.Print("Enter text: ")
    text, _ := reader.ReadString('\n')
    return text
}
```
En este ejemplo, vemos cuando nuestro método `sendString` pasa al canal `msg` el valor del texto que leímos por consola. Luego, en el thread principal de la función `main` al recibir el valor, desbloqueamos el thread principal que queda bloqueado.

### Wait Groups
En cuanto a los wait groups, podemos definir sincronía entre los canales de manera que podamos ejecutarlos y esperar en caso de haber dependencia entre ellos. El funcionamiento es tan simple como declarar nuestro `WaitGroup` y después agregar rutinas a nuestro `WaitGroup`, posteriormente **tenemos que avisar a nuestro grupo que cada rutina terminó**.

```go
package main

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

func main() {
    var resp1 string
    var resp2 string
    // Declaramos nuestro WaitGroup
    wg := sync.WaitGroup{}
    // Avisamos que el límite será 2 rutinas
    wg.Add(2)
    go func() {
        resp1 = askToService1()
        // Avisamos que esta rutina terminó
        wg.Done()
    }()
    go func() {
	resp2 = askToService2()
        // Avisamos que esta rutina terminó
        wg.Done()
    }()
    // Esperamos a que las ejecuciones terminen
    wg.Wait()
    // Seguimos la ejecución
    fmt.Printf("Response 1: %s\n", resp1)
    fmt.Printf("Response 2: %s\n", resp2)
    fmt.Println("Another actions...")
}

func askToService1() string {
    time.Sleep(3 * time.Second)
    return "Hello"
}

func askToService2() string {
    time.Sleep(4 * time.Second)
    return "Bye bye"
}
```
Este es un caso práctico que hemos desarrollado en una de mis experiencias laborales, por lo que es un caso práctico y concreto sobre el uso de `WaitGroup`.

### Mutex
Mutex es un término propio de ejecuciones del sistema operativo que se empeña para evitar las condiciones de carrera, lo que haremos es crear mutex para saber cuando un recurso está siendo accedido por otro hilo de ejecución. De esta manera, bloqueamos un recurso, y en caso de que ese recurso ya esté bloqueado vamos a esperar a que esté disponible.

```go
package main

import (
	"fmt"
	"sync"
)

type Service struct {
	// Este mutex será el que bloquee el recurso `counters`
	mu       sync.Mutex
	counters map[string]int
}

func (svc *Service) increase(counterName string) {
	// Acá es donde bloqueamos el recurso
	// En caso de estar bloqueado por otra rutina, esperamos hasta desbloquearlo
	svc.mu.Lock()
	// El defer nos permite ejecutar el comando al terminar la función
	// Y con el mutex.Unlock() desbloqueamos el recurso
	defer svc.mu.Unlock()
	svc.counters[counterName]++
}

func (svc *Service) doIncrement(name string, limit int) {
	for i := 0; i < limit; i++ {
		svc.increase(name)
	}
}

func main() {
	c := Service{
		counters: map[string]int{"counterA": 0, "counterB": 0},
	}

	var wg sync.WaitGroup

	wg.Add(3)
	go func() {
		c.doIncrement("counterA", 1000)
		wg.Done()
	}()
	go func() {
		c.doIncrement("counterA", 1000)
		wg.Done()
	}()
	go func() {
		c.doIncrement("counterB", 1000)
		wg.Done()
	}()

	wg.Wait()
	fmt.Println(c.counters)
}
```
En el ejemplo, se nota como `c.doIncrement("counterA", 1000)` es prácticamente accedido al mismo tiempo 2 veces, para no pisar su valor, el `mu.Lock()` va a esperar hasta que se desbloquee el recurso.

## Conclusión
En este post repasamos algunas de las herramientas de Go para manejo de concurrencia, pero también existen otras herramientas que posiblemente veremos más adelante. Así mismo, planeo adentrarme en más detalle acerca de estas herramientas, por lo que te recomiendo suscribirte y estar atento a los siguientes posts 😊 🔧