Go language implements priority in select statement
Go language implements priority in select statement
Introduction to select statement
The select statement in Go language is used to monitor and select a group of case statements to execute the corresponding code. It looks similar to the switch statement, but the expression in all cases in the select statement must be the send or receive operation of channel. A typical use example of select is as follows:
select { case <-ch1: fmt.Println("liwenzhou.com") case ch2 <- 1: fmt.Println("q1mi") }
The select keyword in the Go language can also make the current goroutine wait for the readability of ch1 and the writability of ch2 at the same time. Before the states of ch1 and ch2 change, the select will be blocked until one of the channels becomes ready to execute the code of the corresponding case branch. If multiple channels are ready at the same time, select a case at random for execution.
In addition to the typical exceptions shown above, let's introduce some special examples of select one by one.
Empty select
Empty select means that there is no case inside, for example:
select{ }
An empty select statement will directly block the current goroutine, making the goroutine enter a permanent sleep state that cannot be awakened.
Only one case
If the select contains only one case, the select becomes a blocked channel read / write operation.
select { case <-ch1: fmt.Println("liwenzhou.com") }
The above code will execute the print operation when ch1 is readable, otherwise it will be blocked.
There is a default statement
If the select can also contain a default statement, it is used to perform some default operations when other case s are not satisfied.
select { case <-ch1: fmt.Println("liwenzhou.com") default: time.Sleep(time.Second) }
The above code will execute the print operation when ch1 is readable, otherwise the code in the default statement will be executed. Here, it is equivalent to a non blocking channel reading operation.
summary
- select does not have any case: permanently block the current goroutine
- select has only one case: blocked send / receive
- There are multiple cases in select: randomly select a case that meets the conditions for execution
- When there is a default in select and other case s are not satisfied: execute the code in the default statement
How to implement priority in select
It is known that when there are multiple cases in select, a case that meets the conditions will be selected randomly for execution.
Now we have a requirement: we have a function that will continuously receive task 1 and task 2 from ch1 and ch2 respectively,
How to ensure that when ch1 and ch2 reach the ready state at the same time, priority is given to task 1 and task 2 is executed when there is no task 1?
Xiao Ming, a senior Go language programmer, scratched his head and wrote the following functions:
func worker(ch1, ch2 <-chan int, stopCh chan struct{}) { for { select { case <-stopCh: return case job1 := <-ch1: fmt.Println(job1) default: select { case job2 := <-ch2: fmt.Println(job2) default: } } } }
The above code realizes the "priority" by nesting two select ions, which seems to meet the requirements of the topic. But there is something wrong with this code. If ch1 and ch2 do not reach the ready state, the whole program will not block, but enter the dead loop.
What shall I do?
Xiao Ming scratched his head again and wrote down another solution:
func worker2(ch1, ch2 <-chan int, stopCh chan struct{}) { for { select { case <-stopCh: return case job1 := <-ch1: fmt.Println(job1) case job2 := <-ch2: priority: for { select { case job1 := <-ch1: fmt.Println(job1) default: break priority } } fmt.Println(job2) } } }
This time, Xiao Ming not only used nested select, but also combined for loop and LABEL to meet the requirements of the topic. When the above code selects the execution job2: = < - CH2 in the outer layer select, it enters the inner layer select loop and continues to try to execute job1: = < - ch1. When ch1 is ready, it will be executed all the time, otherwise it will jump out of the inner layer select.
Practical application scenario
Although I made up the above requirements, there are practical application scenarios for implementing priority in select in actual production, such as K8s controller There are examples of the actual use of the above technique in. Comments have been added at the key points of implementing priority related code in select. The specific logic will not be described in detail here.
// kubernetes/pkg/controller/nodelifecycle/scheduler/taint_manager.go func (tc *NoExecuteTaintManager) worker(worker int, done func(), stopCh <-chan struct{}) { defer done() // When dealing with specific events, we hope that the update operation of Node takes precedence over the update of Pod // Because NodeUpdates have nothing to do with NoExecuteTaintManager, they should be processed as soon as possible // --We don't want the user (or system) to wait until the PodUpdate queue is exhausted before starting to clear the pod from the contaminated Node. for { select { case <-stopCh: return case nodeUpdate := <-tc.nodeUpdateChannels[worker]: tc.handleNodeUpdate(nodeUpdate) tc.nodeUpdateQueue.Done(nodeUpdate) case podUpdate := <-tc.podUpdateChannels[worker]: // If we find that a Pod needs to be updated, we need to empty the Node queue first priority: for { select { case nodeUpdate := <-tc.nodeUpdateChannels[worker]: tc.handleNodeUpdate(nodeUpdate) tc.nodeUpdateQueue.Done(nodeUpdate) default: break priority } } // After the Node queue is cleared, we will process podUpdate tc.handlePodUpdate(podUpdate) tc.podUpdateQueue.Done(podUpdate) } } }