Select in Golang


January 30, 2022, Learn eTutorial
1478

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. 

What is select 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. 

GO : Select

How to declare select in Golang?

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.

GO : Select

What is the difference between select and switch cases?

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 }

Let us see an example program with select and switch cases.


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

How do select work in Golang?

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.

Let us consider a program


//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.

When select blocks a Case or Channel?

The select keyword can block the case statements if they are not ready to operate

Let us understand with an example


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.

Why select a statement in Golang?

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.

Default case in Select?

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.

Timeout mechanism in select

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.

Program using 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

How to check channel is full?

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