The select statement is used to handle multiple channel operations simultaneously. It waits until one of the send or receive operations is ready and then executes the corresponding case block. The default case can be used to prevent blocking behavior when no channel operations are ready.
Example: The following example uses select to receive data from a channel.
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "Hello, Go!"
}()
select {
case msg := <-ch:
fmt.Println(msg)
}
}
Output
Hello, Go!
Syntax
select {
case sendOrReceive1:
// Code block
case sendOrReceive2:
// Code block
default:
// Code block
}
Components:
- select: Waits for one of the channel operations to become available.
- case: Represents a send or receive operation on a channel.
- default: Executes when no channel operation is ready.
Note: If multiple channel operations are ready at the same time, Go randomly selects one of them for execution.
Deadlock
A deadlock occurs when all goroutines are blocked and no further progress can be made. This commonly happens when a program attempts to send or receive data from a channel without another goroutine available to complete the corresponding operation.
When this situation occurs, Go terminates the program with an error similar to:
fatal error: all goroutines are asleep - deadlock!
Example: The following example demonstrates how a deadlock occurs when select waits on a channel that never receives any data.
package main
func main() {
c := make(chan int)
select {
case <-c:
}
}
Output
fatal error: all goroutines are asleep - deadlock!
Explanation:
- A channel c is created.
- select waits to receive data from the channel.
- No goroutine is sending any value to c.
- The program remains blocked indefinitely.
- Since all goroutines are blocked, Go reports a deadlock.
Using the default Case
The default case executes immediately when none of the channel operations in select are ready. This prevents the select statement from blocking indefinitely.
Example:
Example: package main
import "fmt"
func main() {
c := make(chan int)
select {
case <-c:
fmt.Println("Received value")
default:
fmt.Println("Default case executed")
}
}
Output
Default case executed
Explanation:
selectattempts to receive data fromc.- No value is available in the channel.
- Instead of blocking, the
defaultblock executes immediately. - The program continues executing normally.
Note: The default case does not eliminate deadlocks in general. It only prevents the select statement from blocking when no channel operations are ready.
Using default with a Nil Channel
A nil channel can never send or receive values. Without a default case, a select statement containing only nil channels blocks forever.
Example:
package main
import "fmt"
func main() {
var c chan int
select {
case value := <-c:
fmt.Println("Value:", value)
default:
fmt.Println("Default case executed")
}
}
Output
Default case executed
Explanation:
- c is declared but not initialized, making it a nil channel.
- A nil channel is never ready for communication.
- Since no channel operation can proceed, default executes immediately.
- Without default, the program would block indefinitely.