Select & Patterns
The select statement lets you wait on multiple channel operations. It's like a switch for channels, enabling powerful concurrent patterns. In this lesson, you'll master select and learn professional concurrency patterns.
The Select Statement
Select waits on multiple channel operations:
Click Run to execute your code
- Waits until one case can proceed
- If multiple cases ready, chooses randomly
- Default case runs if no channel ready
- Empty select blocks forever
Timeout Pattern
Use select with time.After for timeouts:
Click Run to execute your code
Non-blocking Channel Operations
// Non-blocking receive
select {
case msg := <-ch:
fmt.Println("Received:", msg)
default:
fmt.Println("No message")
}
// Non-blocking send
select {
case ch <- msg:
fmt.Println("Sent message")
default:
fmt.Println("Channel full")
}
Common Concurrency Patterns
1. Done Channel Pattern
func worker(done <-chan bool) {
for {
select {
case <-done:
fmt.Println("Worker stopping")
return
default:
// Do work
fmt.Println("Working...")
time.Sleep(time.Second)
}
}
}
func main() {
done := make(chan bool)
go worker(done)
time.Sleep(3 * time.Second)
done <- true // Signal worker to stop
time.Sleep(time.Second)
}
2. Ticker Pattern
func main() {
ticker := time.NewTicker(500 * time.Millisecond)
done := make(chan bool)
go func() {
for {
select {
case <-done:
return
case t := <-ticker.C:
fmt.Println("Tick at", t)
}
}
}()
time.Sleep(2 * time.Second)
ticker.Stop()
done <- true
}
3. Context Pattern
Click Run to execute your code
Common Mistakes
1. Forgetting default in non-blocking select
// β Wrong - blocks if channel not ready
select {
case msg := <-ch:
fmt.Println(msg)
}
// β
Correct - non-blocking
select {
case msg := <-ch:
fmt.Println(msg)
default:
fmt.Println("No message")
}
2. Not handling all channels in loop
// β Wrong - done channel ignored in loop
for {
select {
case msg := <-messages:
fmt.Println(msg)
}
// done channel never checked!
}
// β
Correct - check done channel
for {
select {
case <-done:
return
case msg := <-messages:
fmt.Println(msg)
}
}
Exercise: Rate Limiter
Task: Create a rate limiter using select and ticker.
Requirements:
- Process requests at most once per second
- Use time.Ticker for rate limiting
- Handle 5 requests
- Print when each request is processed
Show Solution
package main
import (
"fmt"
"time"
)
func main() {
requests := make(chan int, 5)
for i := 1; i <= 5; i++ {
requests <- i
}
close(requests)
// Rate limiter: 1 request per second
limiter := time.NewTicker(time.Second)
defer limiter.Stop()
fmt.Println("Rate Limiter: Processing requests...")
fmt.Println("====================================")
for req := range requests {
<-limiter.C // Wait for tick
fmt.Printf("Request %d processed at %s\n", req, time.Now().Format("15:04:05"))
}
fmt.Println("====================================")
fmt.Println("All requests processed!")
}
Summary
- select waits on multiple channel operations
- Random choice if multiple cases ready
- default case makes select non-blocking
- Timeout pattern with time.After
- Done channel signals goroutine termination
- Ticker for periodic operations
- Context for cancellation and timeouts
What's Next?
Congratulations on completing the Concurrency module! You've mastered goroutines, channels, and selectβGo's most powerful features. Next, you'll explore Packages & Standard Library to build real-world applications!
Enjoying these tutorials?