Select Statement in Go Language
In Go, the select
statement allows you to wait on multiple channel operations, such as sending or receiving values. Similar to a switch statement, select
enables you to proceed with whichever channel case is ready, making it ideal for handling asynchronous tasks efficiently.
Example
Consider a scenario where two tasks complete at different times. We’ll use select
to receive data from whichever task finishes first.
package main
import (
"fmt"
"time"
)
func task1(ch chan string) {
time.Sleep(2 * time.Second)
ch <- "Task 1 completed"
}
func task2(ch chan string) {
time.Sleep(4 * time.Second)
ch <- "Task 2 completed"
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go task1(ch1)
go task2(ch2)
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
Output
Task 1 completed
Note: After 2 seconds,
Task 1 completed
will be printed becausetask1
completes beforetask2
. Iftask2
finished first, thenTask 2 completed
would be printed.
Syntax
The select
statement in Go listens to multiple channel operations and proceeds with the first ready case.
select {
case value := <-channel1:
// Executes if channel1 is ready to send/receive
case channel2 <- value:
// Executes if channel2 is ready to send/receive
default:
// Executes if no other case is ready
}
Key Points:
select
waits until at least one channel operation is ready.- If multiple cases are ready, one is chosen at random.
- The
default
case executes if no other case is ready, avoiding a block.
Table of Content
Select Statement Variations
Basic Blocking Behavior
In this variation, we modify the earlier example by removing the select
statement and observe blocking behavior when no channels are ready.
Example
package main
import (
"fmt"
)
func main() {
ch := make(chan string)
select {
case msg := <-ch:
fmt.Println(msg)
default:
fmt.Println("No channels are ready")
}
}
Output
No channels are ready
Explanation: Here, the
default
case ensures thatselect
doesn’t block if no channels are ready, printing"No channels are ready"
.
Handling Multiple Cases
If both tasks are ready at the same time, select
chooses one case randomly. This can happen if tasks have similar sleep times.
Example
package main
import (
"fmt"
"time"
)
func portal1(channel1 chan string) {
time.Sleep(3 * time.Second)
channel1 <- "Welcome to channel 1"
}
func portal2(channel2 chan string) {
time.Sleep(9 * time.Second)
channel2 <- "Welcome to channel 2"
}
func main() {
R1 := make(chan string)
R2 := make(chan string)
// calling function 1 and function 2 in goroutine
go portal1(R1)
go portal2(R2)
select {
// case 1 for portal 1
case op1 := <-R1:
fmt.Println(op1)
// case 2 for portal 2
case op2 := <-R2:
fmt.Println(op2)
}
}
Output
Welcome to channel 1
Explanation: In this scenario,
select
randomly picks one of the two cases if both are ready at the same time, meaning you may see"Task 1 completed"
or"Task 2 completed"
depending on the selection.
Using Select with Default Case to Avoid Blocking
Using default
ensures the program does not block if no case is ready. Here’s an example modifying the base structure to include a default
case.
Example
package main
import (
"fmt"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
default:
fmt.Println("No tasks are ready yet")
}
}
Output
No tasks are ready yet
Explanation: Since neither
ch1
norch2
is ready, thedefault
case executes, outputting"No tasks are ready yet"
.
Infinite Blocking without Cases
If a select
statement is empty (i.e., contains no cases), it blocks indefinitely. This is often used in cases where an infinite wait is necessary, but here’s what it looks like using our channels.
package main
func main() {
select {} // This blocks forever as there are no cases
}
Explanation: Since there are no cases, select will block permanently, causing a deadlock if there are no other goroutines active.