The select statement is Go’s mechanism for multiplexing channel operations. It enables handling multiple channels concurrently. For more context, see Go Installation Guide, Go Ecosystem Overview, Go Best Practices.
Basic Select
Waiting for Multiple Channels
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("Received from ch1:", msg1)
case msg2 := <-ch2:
fmt.Println("Received from ch2:", msg2)
}
}
}
Select with Default
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
select {
case value := <-ch:
fmt.Println("Received:", value)
default:
fmt.Println("No value available")
}
// Non-blocking send
select {
case ch <- 42:
fmt.Println("Sent")
default:
fmt.Println("Channel full")
}
}
Timeouts
Using time.After
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
select {
case msg := <-ch:
fmt.Println("Received:", msg)
case <-time.After(1 * time.Second):
fmt.Println("Timeout!")
}
}
Timeout with Goroutine
package main
import (
"fmt"
"time"
)
func fetchData(ch chan string) {
time.Sleep(2 * time.Second)
ch <- "Data received"
}
func main() {
ch := make(chan string)
go fetchData(ch)
select {
case data := <-ch:
fmt.Println(data)
case <-time.After(1 * time.Second):
fmt.Println("Request timeout")
}
}
Ticker
Using time.Tick
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for i := 0; i < 3; i++ {
select {
case <-ticker.C:
fmt.Println("Tick", i+1)
}
}
}
Ticker with Timeout
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
timeout := time.After(2 * time.Second)
for {
select {
case <-ticker.C:
fmt.Println("Tick")
case <-timeout:
fmt.Println("Timeout")
return
}
}
}
Advanced Patterns
Fan-In Pattern
package main
import (
"fmt"
"time"
)
func producer(id int, ch chan string) {
for i := 0; i < 3; i++ {
time.Sleep(time.Duration(id) * 100 * time.Millisecond)
ch <- fmt.Sprintf("Producer %d: message %d", id, i)
}
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go producer(1, ch1)
go producer(2, ch2)
for i := 0; i < 6; i++ {
select {
case msg := <-ch1:
fmt.Println(msg)
case msg := <-ch2:
fmt.Println(msg)
}
}
}
Worker Pool with Select
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- string) {
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(100 * time.Millisecond)
results <- fmt.Sprintf("Worker %d completed job %d", id, job)
}
}
func main() {
jobs := make(chan int, 5)
results := make(chan string, 5)
// Start workers
for i := 1; i <= 3; i++ {
go worker(i, jobs, results)
}
// Send jobs
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
// Collect results
for i := 0; i < 5; i++ {
fmt.Println(<-results)
}
}
Select with Nil Channels
Disabling Channels
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "one"
close(ch1)
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "two"
close(ch2)
}()
var ch1Open, ch2Open bool = true, true
for ch1Open || ch2Open {
select {
case msg, ok := <-ch1:
if !ok {
ch1 = nil // Disable ch1
ch1Open = false
} else {
fmt.Println("ch1:", msg)
}
case msg, ok := <-ch2:
if !ok {
ch2 = nil // Disable ch2
ch2Open = false
} else {
fmt.Println("ch2:", msg)
}
}
}
}
Best Practices
✅ Good: Use Select for Multiplexing
// DO: Use select for multiple channels
select {
case msg := <-ch1:
fmt.Println(msg)
case msg := <-ch2:
fmt.Println(msg)
case <-time.After(1 * time.Second):
fmt.Println("Timeout")
}
✅ Good: Always Handle Timeouts
// DO: Include timeout in select
select {
case result := <-ch:
fmt.Println(result)
case <-time.After(5 * time.Second):
fmt.Println("Operation timed out")
}
✅ Good: Use Default for Non-Blocking
// DO: Use default for non-blocking operations
select {
case msg := <-ch:
fmt.Println(msg)
default:
fmt.Println("No message available")
}
❌ Bad: Blocking Without Timeout
// DON'T: Block indefinitely
msg := <-ch // Could hang forever
Summary
The select statement provides:
- Multiplexing multiple channels
- Timeouts with time.After
- Non-blocking operations with default
- Ticker support for periodic tasks
- Advanced patterns like fan-in/fan-out
These features enable sophisticated concurrent patterns in Go.
Comments