# WaitGroups Go

En Go, es común ejecutar varias goroutines en paralelo para aprovechar la concurrencia del lenguaje. Sin embargo, coordinar la finalización de estas goroutines puede ser un desafío. Aquí es donde los `WaitGroups` resultan esenciales.

En esta sección aprenderé cómo esperar de manera eficiente a que un conjunto de goroutines terminen su ejecución antes de que el programa continúe mediante los `WG`

### ¿Qué es un WaitGroup?

Un `WaitGroup` es un contador de goroutines que se utilizan para sincronizar la ejecución en Go. Con un `WaitGroup`, puedes decirle al programa que espere a que un conjunto de goroutines termine su trabajo antes de continuar con la siguiente tarea. Este mecanismo es crucial para evitar que el programa termine antes de que las goroutines hayan completado su trabajo.

### Uso Básico de WaitGroups

La funcionalidad de `WaitGroup` se maneja principalmente a través de tres métodos:

* **`Add(delta int)`:** Incrementa el contador del `WaitGroup` por el valor de `delta`.
* **`Done()`:** Decrementa el contador del `WaitGroup` en uno. Este método es típicamente llamado por las goroutines cuando terminan su trabajo.
* **`Wait()`:** Bloquea la ejecución hasta que el contador del `WaitGroup` llegue a cero.

#### Ejemplo Básico: Sincronización de Goroutines

```go
package main

import (
    "fmt"
    "sync"
)

func tarea(id int, wg *sync.WaitGroup) {
    defer wg.Done() // Asegura que Done() se llame cuando la función termine
    fmt.Printf("Goroutine %d está trabajando...\\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1) // Incrementa el contador del WaitGroup
        go tarea(i, &wg) //&w referencia del objeto, para no trabajar con copias
    }

    wg.Wait() // Espera hasta que todas las goroutines hayan terminado
    fmt.Println("Todas las goroutines han terminado")
}
```

#### Explicación del Código

1. **Declaración del `WaitGroup`:** Se declara un `WaitGroup` (`var wg sync.WaitGroup`) para controlar la sincronización de las goroutines.
2. **Incremento del Contador con `Add`:** Dentro del bucle, `wg.Add(1)` incrementa el contador del `WaitGroup` cada vez que se lanza una nueva goroutine.
3. **Decremento del Contador con `Done`:** Dentro de cada goroutine, `defer wg.Done()` asegura que el contador se decremente cuando la goroutine termine.
4. **Espera a la Finalización con `Wait`:** Finalmente, `wg.Wait()` bloquea la ejecución hasta que todas las goroutines hayan llamado a `Done()` y el contador llegue a cero.
5. **Con \&wg paso una referencia al objeto Wg:** ya que al usar `&wg`, estoy pasando la dirección de memoria del objeto `wg`, lo que permite que las goroutines accedan y modifiquen el mismo `WaitGroup` en lugar de trabajar con una copia.

**Salida esperada:**

<pre><code><strong>Goroutine 1 está trabajando...
</strong>Goroutine 2 está trabajando...
Goroutine 3 está trabajando...
Goroutine 4 está trabajando...
Goroutine 5 está trabajando...
Todas las goroutines han terminado
</code></pre>

### Asegurando el Uso Correcto de WaitGroups

#### Uso de `Add` antes de Lanzar la Goroutine

Es importante siempre llamar a `wg.Add(1)` antes de lanzar la goroutine. Si `Add` se llama después, existe la posibilidad de que `Wait` termine antes de que la goroutine se registre en el `WaitGroup`, lo que puede llevar a un comportamiento inesperado.

**Ejemplo Incorrecto:**

```go
package main

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

func tarea(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    time.Sleep(1 * time.Second)
    fmt.Printf("Goroutine %d completada.\\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 3; i++ {
        go tarea(i, &wg) // ¡Error! `wg.Add(1)` debería ir antes de lanzar la goroutine
        wg.Add(1)
    }

    wg.Wait()
    fmt.Println("Todas las goroutines han terminado")
}
```

Este código puede resultar en un comportamiento inesperado porque `wg.Wait()` podría ejecutarse antes de que todas las goroutines llamen a `Add(1)`.

#### Evitar los Panics por Llamadas Incorrectas

Es crucial que el número de llamadas a `wg.Done()` coincida con el número de llamadas a `wg.Add()`. De lo contrario, puedes encontrarte con un `panic` si el contador de `WaitGroup` se vuelve negativo.

**Ejemplo de Error Común:**

```go
package main

import (
    "fmt"
    "sync"
)

func tarea(id int, wg *sync.WaitGroup) {
    fmt.Printf("Goroutine %d está trabajando...\\n", id)
    // ¡Olvidamos llamar a wg.Done() aquí!
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go tarea(i, &wg)
    }

    wg.Wait() // Esto podría bloquearse indefinidamente
    fmt.Println("Todas las goroutines han terminado")
}
```

En este ejemplo, el programa se bloqueará en `wg.Wait()` porque no se llama a `wg.Done()` en la goroutine, lo que impide que el contador llegue a cero.

### Ejemplo Completo: Sincronización de Múltiples Tareas Concurrentes

Para mostrar el poder de los `WaitGroups`, consideremos un ejemplo donde diferentes goroutines realizan diferentes tareas de duración variable.

```go
//Author: @JFOZ1010 -> Github
package main

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

func tareaRapida(wg *sync.WaitGroup) {
	defer wg.Done()             //para decrementar cada que cada go routine termine.justo antes de finalizar la funcion con defer.
	time.Sleep(1 * time.Second) //un tiempo de prueba de 1 segundo.
	fmt.Println("Tarea rápida completada.")
}

func tareaLenta(wg *sync.WaitGroup) {
	defer wg.Done()
	time.Sleep(3 * time.Second)
	fmt.Println("Tarea lenta completada.")
}

func main() {
	var wg sync.WaitGroup

	wg.Add(1)           //incrementamos el contador del wg con cada go routine que se va a ejecutar, en este caso la llamamos en su linea sucesiva con un go routine function.
	go tareaRapida(&wg) //aqui con el anpersand estamos haciendo una referencia al objeto con el fin de acceder a cada rutina en su memoria, para cada go routine.

	wg.Add(1)          //incrementamos el contador del wg con cada go routine que se va a ejecutar, en este caso la llamamos en su linea sucesiva con un go routine function.
	go tareaLenta(&wg) //aqui con el anpersand estamos haciendo una referencia al objeto con el fin de acceder a cada rutina en su memoria, para cada go routine.

	wg.Wait()
	fmt.Println("Todas las tareas han terminado.")
}
```

#### Explicación del Código

1. **Función `tareaRapida`:** Simula una tarea que toma 1 segundo en completarse.
2. **Función `tareaLenta`:** Simula una tarea que toma 3 segundos en completarse.
3. **Sincronización:** Ambas tareas se ejecutan en paralelo, y `wg.Wait()` asegura que el programa no termine hasta que ambas goroutines completen su trabajo.

**Salida esperada:**

```
Tarea rápida completada.
Tarea lenta completada.
Todas las tareas han terminado.
```

### Conclusión

Los `WaitGroups` son una herramienta esencial en Go para manejar la sincronización de goroutines de manera eficiente. Aseguran que un conjunto de tareas concurrentes termine antes de proceder con la siguiente parte del programa, lo que es crucial para evitar problemas comunes en programación concurrente como condiciones de carrera o bloqueos inesperados.

Usando `Add`, `Done` y `Wait` correctamente, puedes escribir código concurrente robusto y fácil de mantener.
