DMarlon Studio

Lección 13 · 13 de 20

Canales con búfer y select · Go

Use canales con búfer como colas en Go, envíe sin bloquear con select y default, y ponga un tiempo límite con select y time.After.

Go · Canales con búfer y select en DMarlon Studio: el código del ejemplo y su salida en la terminal

make(chan string, 3) crea un canal con búfer: admite hasta tres mensajes sin que nadie los reciba todavía. Funciona como una cola de correos pendientes. len(queue) dice cuántos hay dentro y cap(queue), cuántos caben.

`select` espera a la vez en varias operaciones de canal y ejecuta la primera que pueda hacerse. Con un case queue <- msg y un `default`, el envío deja de bloquear: si hay sitio, se encola; si el búfer está lleno, se ejecuta default y el mensaje se descarta. Por eso newsletter y survey no entran.

Después de close(queue) ya no se puede enviar, pero los mensajes que hay en el búfer siguen ahí: range los entrega todos, en orden, y termina cuando la cola queda vacía.

La segunda parte pone un tiempo límite. Una goroutine tarda 300 ms en preparar un informe; time.After(50 * time.Millisecond) devuelve un canal que recibe un valor pasados 50 ms. El select se queda con lo que llegue antes: el plazo. El canal report tiene búfer de 1 para que la goroutine pueda dejar su resultado aunque ya nadie lo espere y no se quede bloqueada para siempre.

Si varios case están listos a la vez, select elige uno al azar. Este ejemplo lo evita a propósito: o solo hay un caso posible, o los tiempos están muy separados (50 frente a 300 ms), y por eso la salida es siempre la misma.

Código · main.go

package mainimport (	"fmt"	"time")func main() {	queue := make(chan string, 3) // buffered: holds 3 messages without a receiver	for _, msg := range []string{"welcome email", "invoice 17", "password reset", "newsletter", "survey"} {		select {		case queue <- msg:			fmt.Printf("queued %-15s len=%d cap=%d\n", msg, len(queue), cap(queue))		default: // the buffer is full: do not block, drop the message			fmt.Println("full, dropped", msg)		}	}	close(queue)	for msg := range queue { // the buffered values are still delivered after close		fmt.Println("sending", msg)	}	report := make(chan string, 1) // buffer 1: the sender never gets stuck	go func() {		time.Sleep(300 * time.Millisecond) // a slow job		report <- "sales report ready"	}()	select { // waits for whichever case is ready first	case r := <-report:		fmt.Println(r)	case <-time.After(50 * time.Millisecond):		fmt.Println("timeout: report not ready after 50ms")	}}

Salida · go run main.go

queued welcome email   len=1 cap=3
queued invoice 17      len=2 cap=3
queued password reset  len=3 cap=3
full, dropped newsletter
full, dropped survey
sending welcome email
sending invoice 17
sending password reset
timeout: report not ready after 50ms

Para recordar

  • make(chan T, n) crea un canal con búfer de n valores.
  • select ejecuta la primera operación de canal que esté lista.
  • Con default, select no bloquea nunca.
  • Tras close, range todavía entrega lo que queda en el búfer.
  • time.After en un select pone un tiempo límite.

Ejercicio

Cambie la capacidad de queue a 5 y compruebe que ya no se descarta nada. Después cambie el plazo a 500 * time.Millisecond y vea cómo ahora gana el informe.