这是解决select的优先级问题的通用习语。
是的,至少可以说不好,但可以做到 100% 所需,没有陷阱,也没有隐藏的限制。
这是一个简短的代码示例,解释如下。
package main
import(
"fmt"
"time"
)
func sender(out chan int, exit chan bool) {
for i := 1; i <= 10; i++ {
out <- i
}
time.Sleep(2000 * time.Millisecond)
out <- 11
exit <- true
}
func main(){
out := make(chan int, 20)
exit := make(chan bool)
go sender(out, exit)
time.Sleep(500 * time.Millisecond)
L:
for {
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
default:
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
case <-exit:
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
default:
fmt.Println("Exiting")
break L
}
}
}
}
fmt.Println("Did we get all 10? Yes.")
fmt.Println("Did we get 11? DEFINITELY YES")
}
而且,这是它的工作原理,main()
上面的注释:
func main(){
out := make(chan int, 20)
exit := make(chan bool)
go sender(out, exit)
time.Sleep(500 * time.Millisecond)
L:
for {
select {
// here we go when entering next loop iteration
// and check if the out has something to be read from
// this select is used to handle buffered data in a loop
case i := <-out:
fmt.Printf("Value: %d\n", i)
default:
// else we fallback in here
select {
// this select is used to block when there's no data in either chan
case i := <-out:
// if out has something to read, we unblock, and then go the loop round again
fmt.Printf("Value: %d\n", i)
case <-exit:
select {
// this select is used to explicitly propritize one chan over the another,
// in case we woke up (unblocked up) on the low-priority case
// NOTE:
// this will prioritize high-pri one even if it came _second_, in quick
// succession to the first one
case i := <-out:
fmt.Printf("Value: %d\n", i)
default:
fmt.Println("Exiting")
break L
}
}
}
}
fmt.Println("Did we get all 10? Yes.")
fmt.Println("Did we get 11? DEFINITELY YES")
}
注意:在玩有优先级的技巧之前,请确保您正在解决正确的问题。
很有可能,它可以以不同的方式解决。
尽管如此,在 Go 中优先选择 select 是一件很棒的事情。只是个梦..
注意:这是一个非常相似的答案https://stackoverflow.com/a/45854345/11729048在这个线程上,但是只有两个 select
-s 是嵌套的,而不是像我一样的三个。有什么不同?我的方法更有效,我们明确希望在每次循环迭代中处理随机选择。
但是,如果高优先级通道没有缓冲,和/或您不希望它上有大量数据,只有零星的单个事件,那么更简单的两阶段习语(如该答案)就足够了:
L:
for {
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
case <-exit:
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
default:
fmt.Println("Exiting")
break L
}
}
}
它基本上是 2 和 3 阶段,第 1 阶段被删除。
再一次:在大约 90% 的情况下,您认为确实需要优先考虑 chan 切换情况,但实际上不需要。
这是一个单行代码,可以包装在一个宏中:
for {
select { case a1 := <-ch_p1: p1_action(a1); default: select { case a1 := <-ch_p1: p1_action(a1); case a2 := <-ch_p2: select { case a1 := <-ch_p1: p1_action(a1); default: p2_action(a2); }}}
}
如果你想优先考虑两个以上的情况怎么办?
那么你有两个选择。第一个 - 使用中间 goroutines 构建一棵树,以便每个 fork 都是二进制的(上面的习语)。
第二种选择是使优先级分叉更多然后加倍。
以下是三个优先级的示例:
for {
select {
case a1 := <-ch_p1:
p1_action(a1)
default:
select {
case a2 := <-ch_p2:
p2_action(a2)
default:
select { // block here, on this select
case a1 := <-ch_p1:
p1_action(a1)
case a2 := <-ch_p2:
select {
case a1 := <-ch_p1:
p1_action(a1)
default:
p2_action(a2)
}
case a3 := <-ch_p3:
select {
case a1 := <-ch_p1:
p1_action(a1)
case a2 := <-ch_p2:
p1_action(a2)
default:
p2_action(a3)
}
}
}
}
}
也就是说,整个结构在概念上分为三个部分,作为原始(二进制)部分。
再一次:机会是,你可以设计你的系统,这样你就可以避免这种混乱。
PS,修辞问题:为什么 Golang 没有将它内置到语言中???问题是修辞问题。