Intermediate · Concurrency: goroutines, channels and context · topic 28 of 46 · 30 min
select: waiting on several channels
Explanation
Why select?
A goroutine often waits for more than one thing: a result OR a timeout, a new job OR a stop signal. Receiving from one channel would block and ignore the other. select is like a switch for channels: it waits until one of its cases can proceed, then runs that case.
select {
case msg := <-sms:
fmt.Println("sms:", msg)
case mail := <-email:
fmt.Println("email:", mail)
}Timeouts with time.After
time.After(d) returns a channel that receives a value once, after duration d. Put it in a select to give up when something takes too long.
result := make(chan string, 1)
go func() {
time.Sleep(2 * time.Second) // slow bank
result <- "payment confirmed"
}()
select {
case r := <-result:
fmt.Println(r)
case <-time.After(500 * time.Millisecond):
fmt.Println("timeout: show 'checking status...' to the user")
}default: do not wait
select {
case job := <-jobs:
process(job)
default:
fmt.Println("no job right now")
}select {
case events <- e:
// sent
default:
dropped++ // queue full: drop instead of slowing the caller
}The for-select loop
func worker(jobs <-chan int, quit <-chan struct{}) {
for {
select {
case j := <-jobs:
fmt.Println("job", j)
case <-quit:
fmt.Println("stopping")
return
}
}
}Closing a channel wakes up every receiver at once, because receiving from a closed channel succeeds immediately. That makes close(quit) a clean way to tell many goroutines to stop. In real code, ctx.Done() from the context package (later in this module) plays this role.
Fan-in: merging channels
func merge(a, b <-chan string) <-chan string {
out := make(chan string)
go func() {
defer close(out)
for a != nil || b != nil {
select {
case v, ok := <-a:
if !ok {
a = nil // a nil channel is never selected again
continue
}
out <- v
case v, ok := <-b:
if !ok {
b = nil
continue
}
out <- v
}
}
}()
return out
}Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
select: waiting on several channels · 1/7
select lets a goroutine wait on several channels at once.
Prefer a video by a teacher? Find free videos on “golang select statement timeout time.After default case” on YouTube ↗
In real life: Fastest of two delivery-partner quotes
A courier aggregator asks two delivery partners for a price quote. It uses whichever answers first, and if neither answers within 800 ms it shows a standard rate, so the checkout page never hangs because a partner is slow.
package main
import (
"fmt"
"time"
)
func quote(name string, delay time.Duration, price int) <-chan string {
ch := make(chan string, 1) // buffered: sender never gets stuck
go func() {
time.Sleep(delay)
ch <- fmt.Sprintf("%s: Rs %d", name, price)
}()
return ch
}
func main() {
a := quote("PartnerA", 300*time.Millisecond, 62)
b := quote("PartnerB", 150*time.Millisecond, 70)
select {
case q := <-a:
fmt.Println("using", q)
case q := <-b:
fmt.Println("using", q)
case <-time.After(800 * time.Millisecond):
fmt.Println("using standard rate Rs 80")
}
}Practice (0/4 done)
Two cases in a select are both ready. Which runs?
What does this print? ch := make(chan int) select { case v := <-ch: fmt.Println(v) default: fmt.Println("empty") }
Why give a result channel a buffer of 1 when the receiver might time out?
Write a function fetchWithTimeout(d time.Duration) (string, error) that starts a goroutine "fetching" a page (sleep 1 second, then send "page") and returns an error "timeout" if d passes first. Call it with 2 seconds and with 200 ms.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Mutex, Once, atomic and the race detectorConcurrency: goroutines, channels and context · same module
- Concurrency interview questions and coding roundsCareers and interviews
- Message passing with channelsRust Programming · Concurrency and async
- Pointers without fearPointers, interfaces and errors
- SELECT and WHERE: finding rowsBackend Development · Databases and SQL basics
- SELECT, columns, aliases and expressionsSQL and Databases · SELECT: filtering and sorting
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What does time.After(d) return?
Answer: A channel that receives once after d
It returns a <-chan time.Time that fires once.
What does a default case do in select?
Answer: Runs immediately if no other case is ready, so select does not block
default makes the select non-blocking.
Why set a channel variable to nil inside a fan-in loop?
Answer: Because operations on a nil channel block forever, so that case is disabled
A nil channel case can never be selected.
What is a goroutine leak?
Answer: A goroutine blocked forever that never ends, holding memory
Leaked goroutines pile up and waste memory over time.
What happens when you send on a closed channel?Related
Answer: panic: send on closed channel
Sending on a closed channel always panics.
What does errgroup.Group.Wait return?Related
Answer: The first non-nil error returned by any Go function
It waits for all functions and returns the first error.
What happens to running goroutines when main returns?Related
Answer: They are stopped because the program exits
The program ends when main returns.
How do several threads send on the same channel?Related
Answer: Clone the Sender for each thread
Sender can be cloned; each clone can move into a thread.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “Concurrency: goroutines, channels and context” before its exam: Mutex, Once, atomic and the race detector, context: timeouts and cancellation.
- Module 6 exam: Concurrency · 15 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Standard library essentials. 46 topics left in the course before the final exam.