DMarlon Studio

Lesson 13 · 13 of 20

Buffered channels and select · Go

Use buffered channels as queues in Go, send without blocking using select and default, and set a time limit with select and time.After.

Go · Buffered channels and select in DMarlon Studio: the example's code and its output in the terminal

make(chan string, 3) creates a buffered channel: it holds up to three messages before anyone receives them. It works like a queue of pending emails. len(queue) tells you how many are inside and cap(queue) how many fit.

`select` waits on several channel operations at once and runs the first one that can go ahead. With a case queue <- msg and a `default`, the send no longer blocks: if there is room, the message is queued; if the buffer is full, default runs and the message is dropped. That is why newsletter and survey do not get in.

After close(queue) you can no longer send, but the messages already in the buffer are still there: range delivers all of them, in order, and stops once the queue is empty.

The second part sets a time limit. A goroutine takes 300 ms to prepare a report; time.After(50 * time.Millisecond) returns a channel that receives a value after 50 ms. The select takes whichever comes first: the deadline. The report channel has a buffer of 1 so the goroutine can leave its result even when nobody is waiting any more, instead of being stuck forever.

If several cases are ready at once, select picks one at random. This example avoids that on purpose: either only one case is possible, or the times are far apart (50 against 300 ms), which is why the output is always the same.

Code · 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")	}}

Output · 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

Key points

  • make(chan T, n) creates a channel with a buffer of n values.
  • select runs the first channel operation that is ready.
  • With default, select never blocks.
  • After close, range still delivers what is left in the buffer.
  • time.After inside a select sets a time limit.

Exercise

Change the capacity of queue to 5 and check that nothing is dropped now. Then change the deadline to 500 * time.Millisecond and watch the report win instead.