Select Statement and Channel Operations
The select statement is Go’s mechanism for multiplexing channel operations. It enables handling multiple channels concurrently.
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