Go Select Statement
Choose between multiple channel operations
🔀 What is Select Statement?
Select statement lets goroutines wait on multiple channel operations simultaneously. It's like a switch statement but for channels, choosing the first available channel operation to execute.
// Basic select example
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "Channel 1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "Channel 2"
}()
select {
case msg1 := <-ch1:
fmt.Println("Received:", msg1)
case msg2 := <-ch2:
fmt.Println("Received:", msg2)
}
}
Select Statement Features
Non-blocking
Use default case for non-blocking operations
select {
case msg := <-ch:
fmt.Println(msg)
default:
fmt.Println("No data")
}
Random Choice
Randomly selects if multiple cases ready
select {
case <-ch1:
// Handle ch1
case <-ch2:
// Handle ch2
}
Timeout
Implement timeouts with time.After
select {
case msg := <-ch:
// Handle message
case <-time.After(5*time.Second):
// Handle timeout
}
Multiplexing
Handle multiple channels in one place
for {
select {
case <-done:
return
case work := <-jobs:
process(work)
}
}
🔹 Basic Select Usage
Select waits for one of its cases to be ready:
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
// Send to ch1 after 1 second
go func() {
time.Sleep(1 * time.Second)
ch1 <- "Fast channel"
}()
// Send to ch2 after 3 seconds
go func() {
time.Sleep(3 * time.Second)
ch2 <- "Slow channel"
}()
// Select will choose the first ready channel
select {
case msg := <-ch1:
fmt.Println("Got from ch1:", msg)
case msg := <-ch2:
fmt.Println("Got from ch2:", msg)
}
}
Output:
Got from ch1: Fast channel
🔹 Default Case (Non-blocking)
Use default case to make select non-blocking:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
// Try to receive without blocking
select {
case msg := <-ch:
fmt.Println("Received:", msg)
default:
fmt.Println("No data available")
}
// Send data in goroutine
go func() {
time.Sleep(1 * time.Second)
ch <- "Hello!"
}()
// Poll for data
for i := 0; i < 5; i++ {
select {
case msg := <-ch:
fmt.Println("Finally got:", msg)
return
default:
fmt.Printf("Waiting... (%d)\n", i+1)
time.Sleep(300 * time.Millisecond)
}
}
}
Output:
No data available Waiting... (1) Waiting... (2) Waiting... (3) Waiting... (4) Finally got: Hello!
🔹 Timeout Pattern
Implement timeouts using time.After:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
// Simulate slow operation
go func() {
time.Sleep(3 * time.Second)
ch <- "Operation completed"
}()
// Wait with timeout
select {
case result := <-ch:
fmt.Println("Success:", result)
case <-time.After(2 * time.Second):
fmt.Println("Timeout: Operation took too long")
}
}
Output:
Timeout: Operation took too long
🔹 Channel Multiplexing
Handle multiple channels in a loop with select:
package main
import (
"fmt"
"time"
)
func main() {
jobs := make(chan string, 5)
done := make(chan bool)
// Send some jobs
jobs <- "Job 1"
jobs <- "Job 2"
jobs <- "Job 3"
close(jobs)
// Process jobs with timeout
go func() {
for {
select {
case job, ok := <-jobs:
if !ok {
fmt.Println("All jobs completed")
done <- true
return
}
fmt.Printf("Processing: %s\n", job)
time.Sleep(500 * time.Millisecond)
case <-time.After(2 * time.Second):
fmt.Println("Worker timeout")
done <- true
return
}
}
}()
<-done
fmt.Println("Program finished")
}
Output:
Processing: Job 1 Processing: Job 2 Processing: Job 3 All jobs completed Program finished
🔹 Select Best Practices
Follow these patterns for effective select usage:
✅ Common Patterns:
- Timeout: Use time.After for operation timeouts
- Non-blocking: Use default case for polling
- Cancellation: Use context.Done() for cancellation
- Fan-in: Merge multiple channels into one
⚠️ Watch Out For:
- Empty select: select{} blocks forever
- Nil channels: Never selected in select
- Closed channels: Always ready for receive
// Fan-in pattern: merge multiple channels
func fanIn(ch1, ch2 <-chan string) <-chan string {
out := make(chan string)
go func() {
defer close(out)
for {
select {
case msg, ok := <-ch1:
if !ok {
ch1 = nil // Disable this case
continue
}
out <- msg
case msg, ok := <-ch2:
if !ok {
ch2 = nil // Disable this case
continue
}
out <- msg
}
if ch1 == nil && ch2 == nil {
return
}
}
}()
return out
}