select permite a una goroutine esperar en múltiples operaciones de canal simultáneamente. Es el equivalente del switch para canales: se bloquea hasta que uno de sus case puede proceder y ejecuta ese bloque. Si varios cases están listos al mismo tiempo, elige uno al azar. Es fundamental para implementar timeouts, cancelaciones y patrones fan-in.
select básico
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(200 * time.Millisecond)
ch1 <- "canal uno"
}()
go func() {
time.Sleep(100 * time.Millisecond)
ch2 <- "canal dos"
}()
select {
case msg := <-ch1:
fmt.Println("Recibido de ch1:", msg)
case msg := <-ch2:
fmt.Println("Recibido de ch2:", msg)
}
// Imprime: Recibido de ch2: canal dos (llega antes)
}
case default: select no bloqueante
Añadir un case default hace que select no se bloquee: si ningún canal está listo, ejecuta el default inmediatamente:
func tryReceive(ch <-chan int) (int, bool) {
select {
case v := <-ch:
return v, true
default:
return 0, false // no habÃa nada disponible
}
}
Timeout con time.After
time.After(d) devuelve un canal que recibe un valor después de la duración d. Combinado con select es la forma más sencilla de implementar un timeout:
func buscarConTimeout(query string) (string, error) {
resultados := make(chan string)
go func() {
resultado := buscarEnBD(query) // operación lenta
resultados <- resultado
}()
select {
case r := <-resultados:
return r, nil
case <-time.After(2 * time.Second):
return "", fmt.Errorf("timeout: la búsqueda tardó más de 2 segundos")
}
}
Fan-in: combinar múltiples canales en uno
Fan-in recoge valores de varios canales y los envÃa por uno solo. select hace que esto sea elegante:
func fanIn(ch1, ch2 <-chan string) <-chan string {
out := make(chan string)
go func() {
for {
select {
case v := <-ch1:
out <- v
case v := <-ch2:
out <- v
}
}
}()
return out
}
Done channel: cancelar goroutines
El patrón done channel permite señalar a múltiples goroutines que deben detenerse. Cerrarlo difunde la señal a todos los receptores simultáneamente:
func worker(id int, trabajo <-chan int, done <-chan struct{}) {
for {
select {
case tarea, ok := <-trabajo:
if !ok {
return
}
fmt.Printf("worker %d procesando tarea %dn", id, tarea)
case <-done:
fmt.Printf("worker %d canceladon", id)
return
}
}
}
func main() {
trabajo := make(chan int, 10)
done := make(chan struct{})
for i := 1; i <= 3; i++ {
go worker(i, trabajo, done)
}
for i := 0; i < 5; i++ {
trabajo <- i
}
time.Sleep(100 * time.Millisecond)
close(done) // señala a todos los workers que paren
time.Sleep(50 * time.Millisecond)
}
select en un bucle: ticker periódico
func main() {
ticker := time.NewTicker(500 * time.Millisecond)
done := time.After(2 * time.Second)
for {
select {
case t := <-ticker.C:
fmt.Println("tick:", t.Format("15:04:05.000"))
case <-done:
ticker.Stop()
fmt.Println("terminado")
return
}
}
}
Prioridad entre cases
Cuando varios cases están listos, select elige uno al azar. Si necesitas prioridad, usa un select anidado o comprueba el canal prioritario con un default antes del select principal:
// prioritario primero, luego el resto
select {
case <-urgente:
manejarUrgente()
default:
select {
case <-urgente:
manejarUrgente()
case v := <-normal:
manejarNormal(v)
}
}
