In this tutorial, you will learn about the select
keyword. To understand this tutorial you should have a sound knowledge of channels and Goroutine in Golang.
In go programming language a select keyword allows you to wait for multiple communication operations of Goroutines. The select in Golang is similar to switch cases. The syntax followed by select statement follows a switch case pattern. The different cases in select statements perform a wait operation for each send or receive operation from a channel.
For example, consider two channels let it be Channel A and Channel B communicating with each other for some send and receive operations. They need to wait also for achieving concurrency and synchronization which is supported in Golang by select statements.
The select keyword defines a select in Golang. The syntax begins with select
keyword which is followed by curly braces with some case statements followed by some expressions.
Syntax
Select {
Case <- first_channel (send or receive)
//block1
Case <- second _channel (send or receive)
//block2
default:
//block3
}
You can notice that the structure of select is similar to the switch case. So let us make it clear by discussing their difference.
select | switch |
---|---|
In Golang Select Goroutines wait on multiple communication operations. | In Golang a switch is a method to represent if-else statements. |
Select is used with channels and Goroutines. | The switch is used with concrete data types. |
Randomly chooses one case from multiple valid options. | In Switch, each case is executed sequentially. |
No fallthrough concept | Require a fallthrough |
Select is non-deterministic, because you cannot predict which case runs first. It selects randomly a case | Switch is deterministic i.e. we can predict which instruction or block get executed in if or if-else statement. |
select syntax select { Case <- first_channel (send or receive) //block1 Case <-second_channel (send or receive) //block2 default: //block3 } | Switch syntax switch statement; expression { case expression1: //action case expression2: //action default: //action } |
import "fmt"
func main() {
ch := make(chan int, 4)
ch <- 1
ch <- 2
ch <- 3
ch <- 4
close(ch)
switch {
//case <-ch: // invalid case <-ch in switch (mismatched types int and bool)
case <-ch == 1:
fmt.Println("Firstswitch")
fallthrough
case <-ch == 2:
fmt.Println("Secondswitch")
}
Output 1 for first run
Output:
Firstswitch Secondswitch Second_select receiver 2
Output 2 for second run
Output:
Firstswitch Secondswitch Second_select receiver 2
The select
keyword chooses a case
in which a channel must be ready to perform a send and receive operation. When many channels are ready to execute i.e. to perform a send and receive operation a case is selected randomly.
//PROGRAM USING SELECT KEYWORD
package main
import (
"fmt"
"time"
)
func main() {
A := make(chan string)
B := make(chan string)
go func(){
time.Sleep(2*time.Second)
A <- "Hello "
}()
go func(){
time.Sleep(1*time.Second)
B <- "Haaiii"
}()
select {
case rec1 := <-A:
fmt.Println("I recived from channel A \n ",rec1)
case rec2 := <-B:
fmt.Println("I received from channel B \n ",rec2)
}
}
Output:
I received from channel B Haaiii
In this program, we created two channels, channel A & channel B respectively. We have already discussed channel creation in our channel tutorial. So we are moving forward without discussing channel concepts again
A := make(chan string) // created channel A
B := make(chan string) // created channel b
The program defines two anonymous functions (a function without names) which are going to be called by two Goroutines. The select keyword is used to wait for the receiving end and wait on the receiving end of channels, i.e. channel A and Channel B depending on which channel returns first or which channel is contained with information first will perform some actions.
go func(){ //anonymous functions
time.Sleep(2*time.Second)
A <- "Hello "
}()
go func(){
time.Sleep(1*time.Second)
B <- "Haaiii"
}()
To channel A we have written information, hello and similarly a string Haaiii is sent to channel B . The program has two functions in which A is waiting for 2-sec writing into the first one & B is waiting 1 sec.
The select keyword
is to wait on the receiving end of these two channels. Inside a select statement, there are switch cases with variable rec1 assigns that waiting on the receiving end of A, if some information is received from channel A it is printed.
Similar way channel B to perform. The output shown above makes the concept clearer.
The select keyword can block the case statements if they are not ready to operate
package main
import "fmt"
func main() {
channel1 := make(chan string) //created channel1
select {
case receive := <-channel1:
fmt.Println(receive)
}
}
Output:
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() /tmp/sandbox2985791004/prog.go:8 +0x36 Program exited.
The above-given go program created a channel called channel1. Using the select statement only you can perform receive operation from channel1.In the above program, no Goroutines are defined, without Goroutine it is unable to perform send operation. So in this program we created channel1 and select is waiting to receive from channel1 but nothing to receive because it lacks Goroutines. This situation turns to deadlock which means the select statement switches to a blocked state.
The select concept is bound with channels and Goroutines. The multiple Goroutines in a go program that sends data tries to communicate through channels. In order to facilitate traffic-free communication, select functionality is included in Golang. It controls the send and receives operations to be executed via channels without any traffic. The select statement receives the data concurrently from one channel and executes it if it is in a ready state.
Note: Select statement bound to channel and Goroutines achieves synchronization and concurrency in go program.
Let us understand with the example discussed when select blocks a Case or Channel in the above session
package main
import "fmt"
func main() {
channel1 := make(chan string)
select {
case receive := <-channel1:
fmt.Println(receive)
default: //default statement
fmt.Println("Executes Default")
}
}
Output:
Executes Default
Note: Like in normal switch cases, if all cases inside select are invalid then print the default statement.
When using the select to read channel, it will definitely do other things after a certain period of time, instead of blocking in select all the time. Below is a simple example:
package main
import (
"fmt"
"time"
)
func main() {
timeout := make(chan bool, 1)
go func() {
time.Sleep(2 * time.Second)
timeout <- true
}()
ch := make(chan int)
select {
case <-ch:
case <-timeout:
fmt.Println("timeout 01")
case <-time.After(time.Second * 1):
fmt.Println("timeout 02")
}
Output:
timeout 01
Create a timeout channel, so that other places can use the trigger timeout channel to finish the select execution, or there is another way to write it through the time. After mechanism.
package main
import (
"fmt"
"time"
)
func main() {
timeout := make(chan bool, 1)
go func() {
time.Sleep(2 * time.Second)
timeout <- true
}()
ch := make(chan int)
select {
case <-ch:
case <-timeout:
fmt.Println("timeout 01")
case <-time.After(time.Second * 1):
fmt.Println("timeout 02")
}
}
Output:
timeout 02
Let us check an example program
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 1)
ch <- 1
select {
case ch <- 2:
fmt.Println("channel value is", <-ch)
fmt.Println("channel value is", <-ch)
default:
fmt.Println("channel blocking")
}
}
Output:
channel blocking
First, declare the channel with buffer size 1, and then drop the value to fill the channel. At this time, you can use the select + default method to ensure whether the channel is full. The above example will output channel blocking, and we will change the program to the following
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 2)
ch <- 1
select {
case ch <- 2:
fmt.Println("channel value is", <-ch)
fmt.Println("channel value is", <-ch)
default:
fmt.Println("channel blocking")
}
}
After changing the buffer size to 2, you can continue to plug the value into the channel.
Output:
channel value is 1 channel value is 2