Goroutines y Channels
Goroutines y Channels
Goroutines
Una goroutine es una funcion que se ejecuta concurrentemente. Son extremadamente ligeras: cada una usa apenas 2-8 KB de stack (vs ~1 MB de un thread del OS).
package main
import (
"fmt"
"time"
)
func saludar(nombre string) {
fmt.Println("Hola,", nombre)
}
func main() {
go saludar("Go") // lanza goroutine
go saludar("Mundo") // lanza otra goroutine
saludar("Main") // ejecuta en goroutine principal
time.Sleep(100 * time.Millisecond) // espera (temporal)
}
La palabra clave go antes de una llamada a funcion la ejecuta como goroutine.
Goroutines anonimas
func main() {
go func() {
fmt.Println("goroutine anonima")
}() // los parentesis invocan la funcion inmediatamente
go func(msg string) {
fmt.Println(msg)
}("con argumento")
time.Sleep(100 * time.Millisecond)
}
Goroutines vs Threads
| Aspecto | Goroutine | Thread del OS |
|---|---|---|
| Stack inicial | 2-8 KB (crece dinamicamente) | ~1 MB (fijo) |
| Creacion | Microsegundos | Milisegundos |
| Scheduling | Go runtime (cooperativo) | Kernel del OS |
| Cantidad tipica | Miles a millones | Cientos a miles |
| Comunicacion | Channels | Shared memory + locks |
| Costo de cambio de contexto | Nanosegundos | Microsegundos |
Channels
Los channels son el mecanismo de comunicacion entre goroutines. Siguen la filosofia de Go:
“No comuniques compartiendp memoria; comparte memoria comunicandote.”
Crear y usar channels
package main
import "fmt"
func main() {
// Crear un channel de strings
ch := make(chan string)
// Enviar desde una goroutine
go func() {
ch <- "mensaje desde goroutine" // enviar al channel
}()
// Recibir en main
msg := <-ch // recibir del channel
fmt.Println(msg)
}
// mensaje desde goroutine
El operador <- se usa para enviar y recibir:
ch <- valor // enviar valor al channel
valor := <-ch // recibir valor del channel
Channels sin buffer (unbuffered)
Un channel sin buffer bloquea al emisor hasta que alguien recibe, y al receptor hasta que alguien envia:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int) // sin buffer
go func() {
fmt.Println("enviando...")
ch <- 42 // bloquea hasta que alguien reciba
fmt.Println("enviado!")
}()
time.Sleep(time.Second) // simula trabajo
fmt.Println("recibido:", <-ch)
}
// enviando...
// (pausa de 1 segundo)
// recibido: 42
// enviado!
Channels con buffer
Un channel con buffer permite enviar sin bloquear mientras haya espacio:
package main
import "fmt"
func main() {
ch := make(chan int, 3) // buffer de 3 elementos
ch <- 1 // no bloquea
ch <- 2 // no bloquea
ch <- 3 // no bloquea
// ch <- 4 // BLOQUEARIA: buffer lleno
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 3
}
| Tipo | Sintaxis | Bloqueo al enviar | Bloqueo al recibir |
|---|---|---|---|
| Sin buffer | make(chan T) | Hasta que alguien reciba | Hasta que alguien envie |
| Con buffer | make(chan T, n) | Cuando el buffer esta lleno | Cuando el buffer esta vacio |
Cerrar channels
Usa close() para indicar que no se enviaran mas valores:
package main
import "fmt"
func generarNumeros(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // indica que no hay mas valores
}
func main() {
ch := make(chan int)
go generarNumeros(ch)
// range itera hasta que el channel se cierre
for num := range ch {
fmt.Println(num)
}
}
// 0
// 1
// 2
// 3
// 4
Detectar channel cerrado
valor, ok := <-ch
if !ok {
fmt.Println("channel cerrado")
}
Reglas de channels cerrados:
| Operacion | Channel abierto | Channel cerrado |
|---|---|---|
Enviar ch <- v | OK (o bloquea) | panic |
Recibir <-ch | OK (o bloquea) | Retorna zero value + false |
Cerrar close(ch) | OK | panic |
Solo el emisor debe cerrar el channel. Nunca el receptor.
Patron done channel
En vez de time.Sleep, usa un channel para sincronizar goroutines:
package main
import "fmt"
func trabajar(done chan bool) {
fmt.Println("trabajando...")
// simula trabajo pesado
resultado := 0
for i := 0; i < 1000000; i++ {
resultado += i
}
fmt.Println("resultado:", resultado)
done <- true // senaliza que termino
}
func main() {
done := make(chan bool)
go trabajar(done)
<-done // espera hasta que la goroutine termine
fmt.Println("programa terminado")
}
Con struct{} para senales puras (cero memoria):
done := make(chan struct{})
go func() {
// trabajo...
close(done) // mas idiomatico que enviar un valor
}()
<-done
Direccionalidad de channels
Puedes restringir channels a solo enviar o solo recibir en las firmas de funciones:
package main
import "fmt"
// solo puede enviar a este channel
func productor(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}
// solo puede recibir de este channel
func consumidor(ch <-chan int) {
for v := range ch {
fmt.Println("recibido:", v)
}
}
func main() {
ch := make(chan int)
go productor(ch)
consumidor(ch)
}
| Tipo | Sintaxis | Operaciones permitidas |
|---|---|---|
| Bidireccional | chan T | Enviar y recibir |
| Solo envio | chan<- T | Solo enviar |
| Solo recepcion | <-chan T | Solo recibir |
Un chan T se convierte implicitamente a chan<- T o <-chan T. La restriccion es solo en la firma, no en la creacion.
Deadlocks comunes
Enviar sin receptor
func main() {
ch := make(chan int)
ch <- 42 // DEADLOCK: nadie recibe
fmt.Println(<-ch)
}
// fatal error: all goroutines are asleep - deadlock!
Recibir sin emisor
func main() {
ch := make(chan int)
fmt.Println(<-ch) // DEADLOCK: nadie envia
}
Channel sin cerrar con range
func main() {
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
// olvido cerrar el channel
}()
for v := range ch { // DEADLOCK: espera mas valores
fmt.Println(v)
}
}
Como evitar deadlocks
- Cada
senddebe tener unreceivecorrespondiente - Cierra los channels cuando termines de enviar
- Usa channels con buffer cuando el emisor no debe esperar
- El Go runtime detecta deadlocks y termina el programa con error
Ejemplo completo: pipeline simple
package main
import "fmt"
func generar(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func duplicar(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * 2
}
close(out)
}()
return out
}
func main() {
numeros := generar(1, 2, 3, 4, 5)
dobles := duplicar(numeros)
for resultado := range dobles {
fmt.Println(resultado)
}
}
// 2
// 4
// 6
// 8
// 10
Resumen
go func()lanza una goroutine; son ligeras y baratas de crear- Los channels (
chan T) comunican goroutines de forma segura - Channels sin buffer sincronizan; con buffer permiten envios sin bloqueo inmediato
close(ch)indica fin de transmision; solo el emisor debe cerrarrange chitera hasta que el channel se cierrechan<-y<-chanrestringen la direccionalidad en las firmas- Usa el patron done channel para sincronizar en vez de
time.Sleep