# Goroutines en Go: Concurrencia Ligera y Poderosa

En los posts anteriores exploramos los fundamentos de Go: structs, interfaces, métodos y colecciones. Hoy vamos a adentrarnos en una de las características más distintivas y poderosas de Go: las **goroutines**. Si vienes de Java, esto cambiará completamente tu forma de pensar sobre la concurrencia.

## **¿Qué son las Goroutines?**

Las goroutines son funciones que se ejecutan **concurrentemente** en Go. Son el equivalente conceptual a los threads en Java, pero con una diferencia crucial: son **extremadamente ligeras**.

### **Comparación: Threads vs Goroutines**

| Aspecto | Threads (Java) | Goroutines (Go) |
| --- | --- | --- |
| Tamaño inicial | ~1-2 MB de stack | ~2 KB de stack |
| Escalabilidad | Miles de threads | Millones de goroutines |
| Gestión | Sistema operativo | Runtime de Go |
| Overhead | Alto | Mínimo |
| Sincronización | Compleja (locks, semáforos) | Simple (channels) |

```go
// Crear una goroutine es tan simple como agregar "go" antes de la función
go sayHello("Goroutine 1")
go sayHello("Goroutine 2")
```

## **Goroutines Básicas**

La sintaxis es increíblemente simple. Solo necesitas la palabra clave `go` antes de una llamada a función:

```go
func sayHello(name string) {
    for i := 0; i < 3; i++ {
        fmt.Printf("Hello from %s (iteration %d)\n", name, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    // Ejecutar función como goroutine (no bloquea)
    go sayHello("Goroutine 1")
    go sayHello("Goroutine 2")
    
    // Esperar un poco para que las goroutines terminen
    // En producción, usarías channels o sync.WaitGroup
    time.Sleep(1 * time.Second)
}
```

**Salida (puede variar):**

```go
Hello from Goroutine 1 (iteration 0)
Hello from Goroutine 2 (iteration 0)
Hello from Goroutine 1 (iteration 1)
Hello from Goroutine 2 (iteration 1)
Hello from Goroutine 1 (iteration 2)
Hello from Goroutine 2 (iteration 2)
```

Las goroutines se ejecutan de forma **concurrente**, no secuencial. El orden de ejecución no está garantizado.

## **Goroutines Anónimas**

Puedes crear goroutines con funciones anónimas, similar a las lambdas en Java:

```go
func main() {
    // Función anónima como goroutine
    go func(msg string) {
        fmt.Println(msg)
    }("Hello from anonymous goroutine")
    
    time.Sleep(100 * time.Millisecond)
}
```

Esto es útil cuando necesitas ejecutar código simple sin definir una función separada.

## **⚠️ Cuidado: Closures y Variables de Loop**

Este es uno de los errores más comunes cuando trabajas con goroutines. Las goroutines capturan variables por **referencia**, no por valor:

### **❌ Problema Común**

```go
func main() {
    // PROBLEMA: Todas las goroutines ven el mismo valor de i
    for i := 0; i < 3; i++ {
        go func() {
            fmt.Printf("Incorrect: i = %d\n", i) // i puede ser cualquier valor (0, 1, 2, o incluso 3)
        }()
    }
    time.Sleep(100 * time.Millisecond)
}
```

**Salida posible:**

```go
Incorrect: i = 3
Incorrect: i = 3
Incorrect: i = 3
```

Todas las goroutines pueden ver el valor final de `i` porque comparten la misma variable.

### **✅ Solución 1: Pasar como Parámetro**

```go
func main() {
    // SOLUCIÓN: Pasar el valor como parámetro
    for i := 0; i < 3; i++ {
        go func(val int) {
            fmt.Printf("Correct: val = %d\n", val)
        }(i) // ← Pasar i como argumento
    }
    time.Sleep(100 * time.Millisecond)
}
```

**Salida:**

```go
Correct: val = 0
Correct: val = 1
Correct: val = 2
```

### **✅ Solución 2: Crear Copia Local**

```go
func main() {
    // SOLUCIÓN: Crear una nueva variable en cada iteración
    for i := 0; i < 3; i++ {
        i := i // ← Crear nueva variable en cada iteración
        go func() {
            fmt.Printf("Correct: i = %d\n", i)
        }()
    }
    time.Sleep(100 * time.Millisecond)
}
```

Ambas soluciones funcionan. La primera es más explícita y generalmente preferida.

## **Sincronización con sync.WaitGroup**

En el ejemplo anterior usamos `time.Sleep()` para esperar que las goroutines terminen. Esto es un **anti-patrón** en producción. La forma idiomática de esperar múltiples goroutines es usando `sync.WaitGroup`.

`WaitGroup` es similar a `CountDownLatch` en Java:

```go
import "sync"

func main() {
    var wg sync.WaitGroup
    
    // Lanzar múltiples goroutines
    for i := 0; i < 5; i++ {
        wg.Add(1) // Incrementar contador
        go func(id int) {
            defer wg.Done() // Decrementar contador cuando termine
            fmt.Printf("Goroutine %d: Starting\n", id)
            time.Sleep(time.Duration(id) * 100 * time.Millisecond)
            fmt.Printf("Goroutine %d: Finished\n", id)
        }(i)
    }
    
    // Esperar a que todas terminen
    fmt.Println("Waiting for all goroutines to finish...")
    wg.Wait() // Bloquea hasta que el contador llegue a 0
    fmt.Println("All goroutines finished!")
}
```

**Cómo funciona:**

1. `wg.Add(1)` - Incrementa el contador interno
    
2. `defer wg.Done()` - Decrementa el contador cuando la goroutine termina
    
3. `wg.Wait()` - Bloquea hasta que el contador llegue a 0
    

**Patrón recomendado:**

* Siempre usa `defer wg.Done()` para asegurar que se llame incluso si hay un panic
    
* Llama `wg.Add()` antes de lanzar la goroutine, no dentro de ella
    

## **Ejemplo Práctico: Procesar Tareas en Paralelo**

Uno de los casos de uso más comunes de las goroutines es procesar múltiples tareas en paralelo:

```go
type Task struct {
    ID   int
    Data string
}

func processTask(task Task) {
    fmt.Printf("Processing task %d: %s\n", task.ID, task.Data)
    time.Sleep(500 * time.Millisecond) // Simular trabajo
    fmt.Printf("Task %d completed\n", task.ID)
}

func main() {
    tasks := []Task{
        {ID: 1, Data: "Task 1"},
        {ID: 2, Data: "Task 2"},
        {ID: 3, Data: "Task 3"},
        {ID: 4, Data: "Task 4"},
        {ID: 5, Data: "Task 5"},
    }
    
    var wg sync.WaitGroup
    start := time.Now()
    
    // Procesar todas las tareas en paralelo
    for _, task := range tasks {
        wg.Add(1)
        go func(t Task) {
            defer wg.Done()
            processTask(t)
        }(task)
    }
    
    wg.Wait()
    elapsed := time.Since(start)
    fmt.Printf("All tasks completed in %v\n", elapsed)
}
```

**Salida:**

```go
Processing task 1: Task 1
Processing task 2: Task 2
Processing task 3: Task 3
Processing task 4: Task 4
Processing task 5: Task 5
Task 1 completed
Task 2 completed
Task 3 completed
Task 4 completed
Task 5 completed
All tasks completed in ~500ms
```

Si procesáramos las tareas secuencialmente, tomaría ~2.5 segundos (5 tareas × 500ms). Con goroutines, todas se ejecutan en paralelo y toma solo ~500ms.

## **Ejemplo: Simulación de Servidor Web**

Las goroutines son perfectas para manejar múltiples requests concurrentes en un servidor web:

```go
type Request struct {
    ID   int
    Path string
}

func handleRequest(req Request) {
    fmt.Printf("[Request %d] Handling %s\n", req.ID, req.Path)
    time.Sleep(200 * time.Millisecond) // Simular procesamiento
    fmt.Printf("[Request %d] Completed\n", req.ID)
}

func main() {
    requests := []Request{
        {ID: 1, Path: "/api/users"},
        {ID: 2, Path: "/api/products"},
        {ID: 3, Path: "/api/orders"},
        {ID: 4, Path: "/api/payments"},
        {ID: 5, Path: "/api/reports"},
    }
    
    var wg sync.WaitGroup
    
    // Simular servidor que maneja requests concurrentemente
    for _, req := range requests {
        wg.Add(1)
        go func(r Request) {
            defer wg.Done()
            handleRequest(r)
        }(req)
    }
    
    wg.Wait()
    fmt.Println("All requests handled")
}
```

Este patrón es exactamente lo que hace el servidor HTTP de Go (`net/http`) internamente. Cada request se maneja en su propia goroutine.

## **Comparación: Java vs Go**

### **Java: Threads Tradicionales**

```java
// Java
public class TaskProcessor {
    public void processTasks(List<Task> tasks) {
        List<Thread> threads = new ArrayList<>();
        
        for (Task task : tasks) {
            Thread thread = new Thread(() -> {
                processTask(task);
            });
            threads.add(thread);
            thread.start();
        }
        
        // Esperar a que todos terminen
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
```

**Problemas:**

* Overhead alto por thread
    
* Limitado a miles de threads
    
* Gestión manual de threads
    
* Manejo de excepciones complejo
    

### **Go: Goroutines**

```go
// Go
func processTasks(tasks []Task) {
    var wg sync.WaitGroup
    
    for _, task := range tasks {
        wg.Add(1)
        go func(t Task) {
            defer wg.Done()
            processTask(t)
        }(task)
    }
    
    wg.Wait()
}
```

**Ventajas:**

* Overhead mínimo
    
* Puedes tener millones de goroutines
    
* Gestión automática por el runtime
    
* Sincronización simple con WaitGroup
    

## **¿Cuándo Usar Goroutines?**

✅ **Usa goroutines cuando:**

* Necesitas procesar múltiples tareas en paralelo
    
* Tienes operaciones I/O bloqueantes (archivos, red, bases de datos)
    
* Quieres mejorar el rendimiento de operaciones independientes
    
* Necesitas manejar múltiples conexiones simultáneas
    

❌ **No uses goroutines cuando:**

* Las tareas son muy pequeñas (el overhead puede ser mayor que el beneficio)
    
* Las tareas dependen fuertemente entre sí (puede ser más complejo)
    
* Ya tienes suficiente concurrencia (más goroutines no siempre es mejor)
    

## **Mejores Prácticas**

### **1\. Siempre Usa WaitGroup para Sincronización**

```go
// ❌ MAL
go doSomething()
time.Sleep(1 * time.Second) // ¿Qué pasa si toma más tiempo?

// ✅ BIEN
var wg sync.WaitGroup
wg.Add(1)
go func() {
    defer wg.Done()
    doSomething()
}()
wg.Wait()
```

### **2\. Maneja Errores Correctamente**

```go
// ✅ BIEN: Usar channels para errores
errChan := make(chan error, len(tasks))
var wg sync.WaitGroup

for _, task := range tasks {
    wg.Add(1)
    go func(t Task) {
        defer wg.Done()
        if err := processTask(t); err != nil {
            errChan <- err
        }
    }(task)
}

go func() {
    wg.Wait()
    close(errChan)
}()

// Procesar errores
for err := range errChan {
    log.Printf("Error: %v", err)
}
```

### **3\. Limita el Número de Goroutines Concurrentes**

Lanzar millones de goroutines puede ser contraproducente. Usa un **worker pool** o **semáforo**:

```go
// Limitar a 10 goroutines concurrentes
semaphore := make(chan struct{}, 10)
var wg sync.WaitGroup

for _, task := range tasks {
    wg.Add(1)
    go func(t Task) {
        defer wg.Done()
        semaphore <- struct{}{}        // Adquirir
        defer func() { <-semaphore }() // Liberar
        
        processTask(t)
    }(task)
}

wg.Wait()
```

### **4\. Usa Context para Cancelación**

```go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

var wg sync.WaitGroup
for _, task := range tasks {
    wg.Add(1)
    go func(t Task) {
        defer wg.Done()
        
        select {
        case <-ctx.Done():
            return // Cancelar si el contexto expira
        default:
            processTask(t)
        }
    }(task)
}
```

## **Errores Comunes**

### **❌ Error 1: No Esperar que las Goroutines Terminen**

```go
// ❌ MAL: El programa puede terminar antes que las goroutines
func main() {
    go doSomething()
    // Programa termina aquí
}

// ✅ BIEN: Esperar con WaitGroup
func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        doSomething()
    }()
    wg.Wait()
}
```

### **❌ Error 2: Capturar Variables de Loop Incorrectamente**

```go
// ❌ MAL: Todas ven el mismo valor
for i := 0; i < 10; i++ {
    go func() {
        fmt.Println(i) // Puede imprimir 10 diez veces
    }()
}

// ✅ BIEN: Pasar como parámetro
for i := 0; i < 10; i++ {
    go func(val int) {
        fmt.Println(val)
    }(i)
}
```

### **❌ Error 3: Olvidar Defer en WaitGroup.Done()**

```go
// ❌ MAL: Si hay un panic, Done() nunca se llama
go func() {
    wg.Done() // Puede no ejecutarse si hay panic antes
    riskyOperation()
}()

// ✅ BIEN: Usar defer
go func() {
    defer wg.Done() // Siempre se ejecuta
    riskyOperation()
}()
```

## **Conclusiones**

Las goroutines son una de las características más poderosas de Go:

✅ **Simplicidad**: Crear una goroutine es tan simple como agregar `go` antes de una función

✅ **Eficiencia**: Extremadamente ligeras, puedes tener millones ejecutándose simultáneamente

✅ **Sincronización**: `sync.WaitGroup` hace que esperar múltiples goroutines sea trivial

✅ **Idiomático**: Las goroutines son la forma natural de hacer concurrencia en Go

✅ **Escalabilidad**: Perfectas para servidores web, procesamiento de datos, y operaciones I/O

Si vienes de Java, las goroutines pueden parecer mágicas al principio, pero una vez que entiendas cómo funcionan y cómo sincronizarlas correctamente, verás cómo simplifican enormemente la programación concurrente.

## **Próximos Pasos**

En el siguiente post exploraremos los **channels**, el mecanismo de comunicación entre goroutines. Los channels son la forma idiomática de Go para compartir datos de forma segura entre goroutines y evitar race conditions.

---

**¿Has usado goroutines en tus proyectos de Go?** Comparte tus experiencias y casos de uso en los comentarios. Y si quieres ver el código completo de estos ejemplos, puedes encontrarlo en mi repositorio [go-mastery-lab](https://github.com/joedayz/go-mastery-lab).
