Concurrencia y multihilo en Go: goroutines y canales

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción a la concurrencia en Go

El lenguaje Go (Golang) fue desarrollado en Google teniendo en cuenta los requisitos modernos de rendimiento y escalabilidad. Una de las características clave de Go es el soporte integrado para concurrencia (concurrency) — la capacidad de un programa para ejecutar múltiples tareas simultáneamente. A diferencia del multihilo tradicional, donde los hilos son gestionados por el sistema operativo, Go ofrece goroutines ligeras y primitivas de sincronización convenientes — canales.

En este artículo analizaremos:

  • Qué son las goroutines y cómo ejecutarlas
  • Canales para intercambiar datos entre goroutines
  • Mutex para acceso seguro a recursos compartidos
  • Ejemplos prácticos y patrones de código concurrente


1. Goroutines: hilos ligeros de Go

Una goroutine es una función que se ejecuta concurrentemente con otras goroutines. Ocupa solo unos pocos kilobytes de pila (a diferencia de los hilos del SO, donde la pila puede ser de megabytes). Puedes iniciar una goroutine simplemente añadiendo la palabra clave go antes de la llamada a la función.



Ejemplo: Iniciar goroutines

package main

import ( "fmt" "time")

func sayHello(name string) { for i := 0; i < 3; i++ { fmt.Printf("¡Hola, %s! (iteración %d)", name, i) time.Sleep(100 * time.Millisecond) }}

func main() { go sayHello("Alicia") // Iniciar goroutine go sayHello("Roberto") // Otra goroutine time.Sleep(1 * time.Second) // Esperar a que terminen fmt.Println("Función principal finalizada")}

Importante: El programa termina cuando finaliza la función main, incluso si otras goroutines aún están ejecutándose. Por eso usamos time.Sleep para esperar. En código real es mejor usar sync.WaitGroup.



Uso de sync.WaitGroup

package main

import ( "fmt" "sync")

func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // Disminuir el contador al finalizar fmt.Printf("Trabajador %d comenzó a trabajar", id) // ... algún trabajo ... fmt.Printf("Trabajador %d terminó de trabajar", id)}

func main() { var wg sync.WaitGroup

for i := 1; i <= 3; i++ { wg.Add(1) // Incrementar el contador antes de iniciar la goroutine go worker(i, &wg) }

wg.Wait() // Bloquear main hasta que todas las goroutines terminen fmt.Println("Todos los trabajadores han terminado su trabajo")}


2. Canales: intercambio de datos entre goroutines

Los canales (channels) son "tuberías" tipadas a través de las cuales las goroutines pueden enviar y recibir valores. Los canales proporcionan sincronización segura sin bloqueos explícitos.



Creación y uso de canales

package main

import "fmt"

func main() { // Crear un canal para enteros ch := make(chan int)

// Iniciar una goroutine que envía datos go func() { ch <- 42 // Enviar valor al canal }()

// Recibir valor del canal value := <-ch fmt.Println("Recibido:", value)}

Por defecto, los canales son no almacenados en búfer: el envío se bloquea hasta que el receptor esté listo para recibir los datos, y viceversa. Esto garantiza la sincronización.



Canales con búfer

package main

import "fmt"

func main() { // Canal con búfer para 3 elementos ch := make(chan string, 3)

// Enviar sin bloqueo (mientras el búfer no esté lleno) ch <- "uno" ch <- "dos" ch <- "tres"

// Leer fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch)}


Cierre de canales y range

package main

import "fmt"

func fibonacci(n int, ch chan int) { a, b := 0, 1 for i := 0; i < n; i++ { ch <-

Blogs

Book Recommendations