Un paseo profundo en la concurrencia

La magia de los Goroutines en Go: Un paseo profundo en la concurrencia

Introducción

Imagina codificar un servidor web que debe manejar miles de solicitudes simultáneamente. ¿Cómo manejarías todas esas solicitudes sin bloquear la ejecución de tu servidor? Aquí es donde los Goroutines en Go hacen su aparición. En este artículo, aprenderás cómo usar los Goroutines en Go para ejecutar funciones de manera concurrente, su relación con otros conceptos de concurrencia y veremos varios ejemplos prácticos. Los Goroutines son fundamentales para lograr la concurrencia en Go, y dominarlos te convertirá en un Go developer más eficiente y sólido.

Fundamentos teóricos

Un Goroutine es simplemente una función ligera que se ejecuta en su propia línea de tiempo concurrente tal como lo hace un hilo en otros lenguajes de programación. Portanto los Goroutines son más baratos que los hilos y menos costosos a nivel de recursos. Lanzar un Goroutine es tan simple como anteponer la palabra clave go a la llamada de una función.

func hello() {
    fmt.Println("Hello, Goroutine!")
}

func main() {
    go hello()
}

Usualmente usamos Goroutines en combinación con los “channels” para sincronizar y comunicarnos entre diferentes Goroutines.

Implementación paso a paso

Vamos a doblar un array de números en paralelo iterando cada número en Goroutines diferentes para ilustrar cómo funcionan. Primero, gestionaremos el cálculo en una función separada

func double(n int, res chan<- int) {
    res <- 2 * n
}

Esta función toma un numero y un canal que acepta enteros. Calculamos el doble de n y lo enviamos al canal res.

Nuestro main se ve así:

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    res := make(chan int)

    for _, n := range numbers {
        go double(n, res)
    }

    for range numbers {
        fmt.Println(<-res)
    }
}

Aquí, main se convierte en un Goroutine principal que dispara los Goroutines double. El resultado se envía de vuelta al main a través del canal res.

Casos de uso reales

Goroutines es particularmente útil al hacer solicitudes de API ya que se pueden realizar dichas solicitues en paralelo. Aquí hay un ejemplo de cómo podrías escribir una aplicación que obtiene datos de varias API al mismo tiempo usando los gorutines. Los códigos de cada API han sido omitidos para simplificar el ejemplo.

func getAPI1(res chan<- string) {
    // Lógica de la API
    res <- "Resultado API 1"
}

func getAPI2(res chan<- string) {
    // Lógica de la API
    res <- "Resultado API 2"
}

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

    go getAPI1(res)
    go getAPI2(res)

    for i := 0; i < 2; i++ {
        fmt.Println(<-res)
    }
}

En estos casos, las Goroutines mejoran enormemente la eficiencia de la aplicación ya que las solicitudes ya no están bloqueadas entre sí.

Patrones avanzados

Los Goroutines combinados con los WaitGroup de la biblioteca sync pueden utilizarse para cumplir tareas aún más complejas. WaitGroup es particularmente útil cuando no sabemos de antemano cuántos Goroutines vamos a utilizar.

Plus, Go’s channel itself has a lot of hidden tricks like buffer channels and select statements which we can utilize for more complex patterns and controls.

Conclusión y recursos

Los gorutines son una de las características más atractivas de Go que permiten la concurrencia de una manera muy sencilla y poderosa. Te recomendamos que practiques el uso de Gorutines con ejemplos y proyectos reales para obtener más experiencia.

Para un aprendizaje más profundo, puedes referirte a la documentación oficial de Go, y este fantástico curso en línea en Coursera que cubre Goroutines, channels, y mucho más. ¡Feliz codificación!