Select

Select

In the Go programming language, select is a crucial feature for implementing Go's concurrency. It allows a goroutine to read from or write to one or more channels. It closely resembles the syntax of a switch statement. select blocks execution until one of the channel operations can proceed.

When multiple concurrent operations arrive at a select, it will choose one arbitrarily, not necessarily the first defined case.

General syntax

select {
	case value := <- channel:
		// code, runs if this channel has been selected
	case value := <- channel:
		// code, runs if this channel has been selected
	case value := <- channel:
		// code, runs if this channel has been selected
	case value := <- channel:
		// code, runs if this channel has been selected
  ...
	}

select will execute only the code associated with the branch that has been selected and ignore the others, much like a switch.

package main
 
import (
	"fmt"
	"time"
)
 
func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)
 
	go func() {
		time.Sleep(time.Second * 2)
		ch1 <- "Hello"
	}()
 
	go func() {
		time.Sleep(time.Second * 3)
		ch2 <- "Follow The Pattern"
	}()
 
	select {
	case msg := <-ch1:
		fmt.Println(msg)
	case msg := <-ch2:
		fmt.Println(msg)
	}
}

You can run the above example here (opens in a new tab).

In the code above, a simple example of using select is demonstrated, where data is read from two channel variables. The order of data arrival determines which channel's data gets read. In this case, only the value from ch1 is read. The value from ch2 is not read.

package main
 
import (
	"fmt"
	"time"
)
 
func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)
 
	go func() {
		time.Sleep(time.Second * 2)
		ch1 <- "Hello"
	}()
 
	go func() {
		time.Sleep(time.Second * 3)
		ch2 <- "Follow The Pattern"
	}()
 
	for { // infinite loop
		select {
		case msg := <-ch1:
			fmt.Println(msg)
		case msg := <-ch2:
			fmt.Println(msg)
		case <-time.After(time.Second * 4): // timeout
			fmt.Println("times out")
			return
		}
	}
}

You can run the above example here (opens in a new tab).

The code above is a modification of the previous example. By adding an infinite loop, data reading occurs not only from the first channel that sends data but from the others as well, until the timeout is reached. The built-in time library is used to implement timeout behavior with the After function, which returns a channel and sends a signal after the specified time interval (4 seconds in this example). Therefore, only data arriving within the specified time interval will be processed, and those arriving later will not be processed because the program exits due to the return statement.