Welcome To Golang By Example

Select Statement in Go (Golang)

This is the  chapter 25 of the golang comprehensive tutorial series. Refer to this link for other chapters of the series – Golang Comprehensive Tutorial Series

Next Tutorial – Error
Previous Tutorial – Channel

Now let’s check out the current tutorial. Below is the table of contents for current tutorial.

Overview

Select is similar to switch statement, the difference being that in select each of the case statement waits for a send or receive operation from a channel. Select statement will wait until send or receive operation is completed on any one of the case statements. It is different from the switch statement in the way that each of the case statements will either send or receive operation on a channel whereas in switch each of the case statements is an expression.  So a select statement lets you wait on multiple send and receive operations from different channels.  Two important points to note about set a statement is

Below is the format of select

select {
case channel_send_or_receive:
     //Dosomething
case channel_send_or_receive:
     //Dosomething
default:
     //Dosomething
}

Select chooses  the case on which send or receive operation on a channel is not blocked and is ready to be executed. If multiple cases are ready to be executed then one is choosen at random.

Let’s see a simple example. We will study about default case later in this tutorial.

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go goOne(ch1)
    go goTwo(ch2)

    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    }
}

func goOne(ch chan string) {
    ch <- "From goOne goroutine"
}

func goTwo(ch chan string) {
    ch <- "From goTwo goroutine"
}

Output

From goOne goroutine

In the above program, we created two channels that are passed to two different goroutines. Then each of the goroutines is sending one value to the channel. In the select, we have two case statements. Each of the two case statements is waiting for a receive operation to complete on one of the channels. Once any receive operation is complete on any of the channels it is executed and select exits. So as seen from the output, in the above program, it prints the received value from one of the channels and exits.

So in the above program since it is not deterministic which of the send operation will complete earlier that is why you will see different outputs if you run the program multiple times. Let's see another program where we will put timeout in the goroutine goTwo before sending the value to the ch2 channel. This will make sure that the send operation on ch1 will be executed earlier than send operation to ch2.

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go goOne(ch1)
    go goTwo(ch2)

    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    }
}

func goOne(ch chan string) {
    ch <- "From goOne goroutine"
}

func goTwo(ch chan string) {
    time.Sleep(time.Second * 1)
    ch <- "From goTwo goroutine"
}

Output

From goOne goroutine

In the above program, in select statement the receive operation on ch1 is completed earlier and hence select will always be executing that case statement as also evident from the output

It is also possible to wait for receive operation to complete on both the channels by using a for loop across the select statement. Let's see a program for that

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go goOne(ch1)
    go goTwo(ch2)
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println(msg1)
        case msg2 := <-ch2:
            fmt.Println(msg2)
        }
    }
}

func goOne(ch chan string) {
    ch <- "From goOne goroutine"
}

func goTwo(ch chan string) {
    ch <- "From goTwo goroutine"
}

Output

From goOne goroutine
From goTwo goroutine

In the above program, we put a for loop of length two across the select statement. Hence the select statement is executed twice and prints the received value from each of the case statements.

We mentioned earlier that select can block if any of the case statement is not ready. Let's see an example for that

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    }
}

Output

fatal error: all goroutines are asleep - deadlock!

In the above program, we created a channel named ch1.  We then receive from this channel in the select statement.  Since no goroutine  is sending to this channel hence it results in a deadlock and select statement blocks indefinitely.  That is why it gives  below output

fatal error: all goroutines are asleep - deadlock!

Use of  select statement

The select statement is useful if there are multiple goroutines that are sending data to multiple channels concurrently. The select statement can then receive the data concurrently from any of one goroutine and execute the statement which is ready. So select along with channels and goroutines become a very powerful tool for managing synchronization and concurrency.

Select with send operation example

So far we have seen examples of receive operation in select case statements. Let's see an example of send operation as well

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go goOne(ch1)
    go goTwo(ch2)
    select {

    case msg1 := <-ch1:
        fmt.Println(msg1)
    case ch2 <- "To goTwo goroutine":
    }
}

func goOne(ch chan string) {
    ch <- "From goOne goroutine"
}

func goTwo(ch chan string) {
    msg := <-ch
    fmt.Println(msg)
}

Output

To goTwo goroutine

Select with default case

Similar to switch select can also have a default case.  This default case will be executed if no send it or receive operation is ready on any of the case statements. So in a way default statement prevents the select from blocking forever.  So a very important point to note is that the default statement makes the select non-blocking. If the select statement doesn't contain a default case then it can block forever until one send or receive operation is ready on any case statement. Let's see an example to fully understand it

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    default:
        fmt.Println("Default statement executed")
    }
}

Output

Default statement executed

In the above program have a select statement which is waiting for receive operation on ch1 and a default statement. Since no goroutine is sending to channel ch1, hence the default case is executed and select exits. If default case were not there the select would have blocked.

Select with blocking timeout

Blocking timeout in select can be achieved by using After() function of time package. Below  is the signature of After() function.

func After(d Duration) <-chan Time

The After function waits for d duration  to finish and then it returns the current time on a channel

https://golang.org/pkg/time/#Time.After

Let's see a program of select with timeout

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan string)
	go goOne(ch1)

	select {
	case msg := <-ch1:
		fmt.Println(msg)
	case <-time.After(time.Second * 1):
		fmt.Println("Timeout")
	}
}

func goOne(ch chan string) {
	time.Sleep(time.Second * 2)
	ch <- "From goOne goroutine"
}

Output

Timeout

In the above select statement we are waiting for receive operation to complete on ch1. In other case statement we have time.After with the duration of 1 second. So essentially this select statement will wait for at least 1 second for receive operation to complete on ch1 and after that the time.After case statement will be executed.  We have put a timeout of more than 1 seconds in the goOne function and hence we  see the time.After statement getting executed and

Timeout

getting printed as output

Empty select

Select block without any case statement is empty select. The empty select will block forever as there is no case statement  to execute. It is also one of the way for a goroutine to wait indefinitely. But if this empty select is put in the main goroutine then it will cause a deadlock. Let's see a program

package main

func main() {
    select {}
}

Output

fatal error: all goroutines are asleep - deadlock!

In the above program we have empty select and hence it results in a deadlock that is why you see the output as below

fatal error: all goroutines are asleep - deadlock!

Select statement with an infinite for loop outside

We can have infinite for loop outside a select statement. This will cause the select statement to execute indefinite number of times.So when using for statement with infinite loop outside the select statement, we need to have a way yo break out of the for loop. One of the use case of having infinite for loop outside select statement could be that you are waiting for multiple operations to receive on the same channel for a certain time. See below example

package main

import (
	"fmt"
	"time"
)

func main() {
	news := make(chan string)
	go newsFeed(news)

	printAllNews(news)
}

func printAllNews(news chan string) {
	for {
		select {
		case n := <-news:
			fmt.Println(n)
		case <-time.After(time.Second * 1):
			fmt.Println("Timeout: News feed finished")
			return
		}
	}
}

func newsFeed(ch chan string) {
	for i := 0; i < 2; i++ {
		time.Sleep(time.Millisecond * 400)
		ch <- fmt.Sprintf("News: %d", i+1)
	}
}

Output

News: 1
News: 2
Timeout: News feed finished

In the above program, we have created a channel named news  which will hold data of string type. Then we pass this channel to the newsfeed function which is pushing the news feed to this channel . In the select statement, we are receiving the news feed from the news channel. This select statement is inside an infinite for loop  so the select statement will be executed multiple times until we  exit out of for loop . We also have time.After with a duration for 1 second as one of the case statements. So this set up will receive all the news from the news channel for  1  second and then exit. 

Select statement with a nil channel

Send or receive operation on nil channel blocks forever. Hence a use case of having a nil channel in the select statement is to disable that case statement after the the send or receive operation is completed on that case statement. The channel then can simply be set to nil. That case statement will be  ignored when the select statement is executed again and receive or send operation will be waited on another case statement. So it is meant to ignore that case statement and execute the other case statement

package main

import (
    "fmt"
    "time"
)

func main() {
    news := make(chan string)
    go newsFeed(news)
    printAllNews(news)
}

func printAllNews(news chan string) {
    for {
        select {
        case n := <-news:
            fmt.Println(n)
            news = nil
        case <-time.After(time.Second * 1):
            fmt.Println("Timeout: News feed finished")
            return
        }
    }
}

func newsFeed(ch chan string) {
    for i := 0; i < 2; i++ {
        time.Sleep(time.Millisecond * 400)
        ch <- fmt.Sprintf("News: %d", i+1)
    }
}

Output

News: 1
Timeout: News feed finished

The above program is pretty much similar to the program we studied above related to having a select statement inside infinite for loop. The only change being that  after we receive the first news, we disabled the case statement by setting the news channel to nil.

case n := <-news:
   fmt.Println(n)
   news = nil

Hence  we only received the first news and after that, it times out.  This is the use case of nil channel in the select statement

Break keyword in Select

Below is the break keyword example.

import "fmt"

func main() {
	ch := make(chan string, 1)
	ch <- "Before break"

	select {
	case msg := <-ch:
		fmt.Println(msg)
		break
		fmt.Println("After break")
	default:
		fmt.Println("Default case")
	}
}

Output

Before break

break statement will terminate the execution of innermost statement and below line below will never be executed

fmt.Println("After break")

Conclusion

This is all about the select statement in golang. Hope you have liked this article. Please share feedback/improvements/mistakes in comments

Next Tutorial – Error
Previous Tutorial – Channel