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.After
function, you can implement a timeout mechanism. - Non-blocking Communication: By using the
default
clause, 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 case
s, these case
s 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
if
statements using afor
loop, 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 aselect
statement is executed, the Go runtime needs to register each channel operation with the scheduler queue and dequeue it when the channel becomes available. For eachselect
statement, 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
Whenselect
blocks 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’sselect
statement 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,select
is used to block listening to goroutines.Deeper understanding:
select
is 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