Mastering Go’s Select: Beyond the Basics
  
Medium Link: Select Notions You Should Know in Golang | by Wesley Wei | Sep, 2024 | Programmer’s Career
Author:Wesley Wei – LinkedIn, Wesley Wei – Medium, Wesley Wei – Twitter
Hello, here is Wesley, Today’s article is about select in Go. Without further ado, let’s get started.💪
1.1 Select Introduction
In the Go language, the select statement is used to handle multiple channel operations and simplify concurrent programming’s communication and synchronization issues. The select statement is similar to the switch statement, but each case must be a channel operation.
Here is the basic syntax of the select statement:
1  | select { | 
Use Cases
- Multiple Channel Selection: You can wait for multiple channel operations simultaneously, and when any one of them is ready, the program will execute the corresponding case.
 - Timeout Handling: By combining with the 
time.Afterfunction, you can implement a timeout mechanism. - Non-blocking Communication: By using the 
defaultclause, you can achieve non-blocking channel operations. 
Regarding timeout handling, please refer to the article: Time Control You Should Know in Golang | by Wesley Wei | Programmer’s Career
1.2 Select and Channels
Unlike switch, although select also has multiple cases, these cases must be related to channel operations (i.e., read-write operations). It can help developers avoid deadlocks caused by waiting for a blocked channel.
Example 1: Receiving Data from Multiple Channels
Here is an example that shows how to use the select statement to receive data from two channels:
1  | package main | 
In this example, we create two channels chan1 and chan2, and through two goroutines, send messages to these channels at different times. The select statement will wait for the first channel to be ready and receive a message, then output the corresponding information.
Example 2: Handling Timeouts
Below is an example that demonstrates how to use the select statement with a timeout mechanism to handle multiple channel operations:
1  | package main | 
In this example, the select statement waits for data to be read from channels chan1 and chan2. If more than 1 second has passed without any channel being ready, the select statement will execute the third case, printing “Timeout, no channel was ready within 1 second”.
Example 3: Non-Blocking Communication
Finally, let’s look at an example that demonstrates how to use a default clause to implement non-blocking channel operations:
1  | package main | 
In this example, if channel chan1 has no data to receive, the select statement will execute the default clause, printing “Default case: Channel is not ready”.
1.3 Implementation of select
1.3.1 Data Structure
There is no corresponding struct in Go’s source code for select, but we use the runtime.scase struct to represent the case structure within the select control structure:
1  | type scase struct { | 
Since non-default cases all relate to Channel sending and receiving, the runtime.scase struct also contains a runtime.hchan type field to store the Channel used in each case.
1.3.2 Common Flow
In the default situation, the compiler will use the following flow to handle the select statement:
- Convert all cases into runtime.scase structures containing Channel and type information;
 - Call the runtime function runtime.selectgo to select one executable runtime.scasestructure from multiple ready Channels;
 - Generate a set of 
ifstatements using aforloop, which judges whether oneself is the selected case. 
1.3.3 Random Polling and Locking Order
The runtime.selectgo  function first performs the necessary initialization operations and determines the two orders for handling cases: polling order pollOrder and locking order lockOrder:
The polling order pollOrder and locking order lockOrder are determined by the following methods:
1  | func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) { | 
- Polling order: introduces randomness using the runtime.cheaprandn function;
 - Locking order: sorts Channels based on their addresses to avoid deadlocks.
 
Random polling can prevent Channel starvation and ensure fairness, while sorting Channels by address can prevent deadlock occurrence. This code finally calls the runtime.sellockfunction to lock all Channels in the determined order.
1.4 Performance Considerations:
Registration and Selection Overhead
When aselectstatement is executed, the Go runtime needs to register each channel operation with the scheduler queue and dequeue it when the channel becomes available. For eachselectstatement, the registration and selection process incurs additional scheduling overhead.Random Selection Overhead
When multiple channels are ready simultaneously, the Go runtime will randomly select one channel to execute. This random selection involves generating a random number and scanning all ready channels, which introduces additional overhead.Blocking and Waking Up Overhead
Whenselectblocks waiting for a channel, the Go runtime needs to put the current goroutine into the wait queue until the channel has data available. The blocking and waking up process involves scheduling goroutines, which also incurs some context switch overhead.
1.5 I/O Multiplexing:
After analyzing the above, you may have a familiar feeling; no mistake, I also thought of I/O multiplexing. In operating systems, select is a system call, and we often use select, poll, and epoll functions to build an I/O multiplexing model to improve program performance.
The Go language’s select is similar to the system call in operating systems, such as C’s select system call, which can simultaneously listen for multiple file descriptors’ readability or writability. Similarly, Go’s select allows goroutines to simultaneously wait for multiple channels to be readable or writable.
Their abstract form is similar to the following diagram:

1.6 Conclusion:
On the surface:
Go’sselectstatement is a specialized statement only used for sending and receiving messages on channels, which runs blocking; when there are no case statements inselect, it will block the current goroutine. Therefore,selectis used to block listening to goroutines.Deeper understanding:
selectis Go’s mechanism for I/O multiplexing at the language level, specifically designed to detect whether multiple readable or writable channels are ready.
1.7 References
[1] Time Control You Should Know in Golang | by Wesley Wei | Programmer’s Career: https://medium.programmerscareer.com/time-control-you-should-know-in-golang-9699de89eece
[2] runtime.scase: https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/runtime/select.go
[4] runtime.hchan: https://cs.opensource.google/go/go/+/release-branch.go1.23:src/runtime/chan.go;l=33
[6] runtime.selectgo: https://cs.opensource.google/go/go/+/release-branch.go1.23:src/runtime/select.go;l=121
[9] runtime.cheaprandn: https://cs.opensource.google/go/go/+/release-branch.go1.23:src/runtime/rand.go;l=255
[10] runtime.sellock: https://cs.opensource.google/go/go/+/release-branch.go1.23:src/runtime/select.go;l=33
[11] Go 语言 select 的实现原理 | Go 语言设计与实现: https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-select
More Series Articles about You Should Know In Golang:
https://wesley-wei.medium.com/list/you-should-know-in-golang-e9491363cd9a
And I’m Wesley, delighted to share knowledge from the world of programming.
Don’t forget to follow me for more informative content, or feel free to share this with others who may also find it beneficial. it would be a great help to me.
Give me some free applauds, highlights, or replies, and I’ll pay attention to those reactions, which will determine whether or not I continue to post this type of article.
See you in the next article. 👋
中文文章: https://programmerscareer.com/zh-cn/golang-select/
Author: Wesley Wei – Twitter Wesley Wei – Medium
Note: Originally written at https://programmerscareer.com/golang-select/ at 2024-09-22 14:59.
Copyright: BY-NC-ND 3.0
        
        
        
        
Comments