我在多个非缓冲通道上使用select时发现
select {
case <- chana:
case <- chanb:
}
即使两个通道都具有数据,但是在处理此选择时,在chana和case chanb中落下的调用也不平衡。
package main
import (
"fmt"
_ "net/http/pprof"
"sync"
"time"
)
func main() {
chana := make(chan int)
chanb := make(chan int)
go func() {
for i := 0; i < 1000; i++ {
chana <- 100 * i
}
}()
go func() {
for i := 0; i < 1000; i++ {
chanb <- i
}
}()
time.Sleep(time.Microsecond * 300)
acount := 0
bcount := 0
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
for {
select {
case <-chana:
acount++
case <-chanb:
bcount++
}
if acount == 1000 || bcount == 1000 {
fmt.Println("finish one acount, bcount", acount, bcount)
break
}
}
wg.Done()
}()
wg.Wait()
}
运行此演示,当其中一个通道完成读/写操作时,另一个通道可能仍保留999-1。
有什么方法可以确保平衡?
Go
select
语句不偏向任何(就绪)情况。引用规范:
如果可以进行一种或多种通信,则可以通过 统一的伪随机选择 来选择可以进行的单个通信。否则,如果存在默认情况,则选择该情况。如果没有默认情况,则“
select”语句将阻塞,直到可以进行至少一种通信为止。
如果可以进行多次通信,则随机选择一个。这不是一个完美的随机分布,并且规范不能保证这一点,但是它是随机的。
您所体验到的是Go
Playground具有GOMAXPROCS=1
(可以在此处验证)和goroutine调度程序没有抢占性的结果。这意味着默认情况下,goroutine不会并行执行。如果遇到阻塞操作(例如,从网络读取或试图从阻塞的通道接收或在其上发送),则将goroutine置于停顿状态,然后继续运行。
而且,由于您的代码中没有阻塞操作,因此goroutines可能不会停放,可能只有您的一个“生产者” goroutines会运行,而另一个可能无法调度。
在本地计算机上运行您的代码GOMAXPROCS=4
,我得到的结果非常“真实”。运行几次,输出:
finish one acount, bcount 1000 901
finish one acount, bcount 1000 335
finish one acount, bcount 1000 872
finish one acount, bcount 427 1000
如果需要确定单个案例的优先级,请查看以下答案:强制执行goselect语句的优先级
的默认行为select
不能保证优先级相等,但是平均而言它将接近它。如果您需要保证相等的优先级,则不应使用select
,但是您可以从2个通道中进行2个非阻塞接收的序列,如下所示:
for {
select {
case <-chana:
acount++
default:
}
select {
case <-chanb:
bcount++
default:
}
if acount == 1000 || bcount == 1000 {
fmt.Println("finish one acount, bcount", acount, bcount)
break
}
}
如果两个电源值均相等,则上述2个非阻塞接收将以相等的速度(具有相同的优先级)消耗2个通道,如果一个不相同,则不断接收来自另一个的信号而不会延迟或阻塞。
需要注意的一件事是,如果 没有_一个通道提供任何要接收的值,则这基本上是一个“忙”循环,因此会消耗计算能力。为避免这种情况,我们可能检测到没有通道准备就绪,_然后select
对两个接收都使用一条语句,然后将阻塞该语句,直到其中一个接收准备就绪,而不会浪费任何CPU资源:
for {
received := 0
select {
case <-chana:
acount++
received++
default:
}
select {
case <-chanb:
bcount++
received++
default:
}
if received == 0 {
select {
case <-chana:
acount++
case <-chanb:
bcount++
}
}
if acount == 1000 || bcount == 1000 {
fmt.Println("finish one acount, bcount", acount, bcount)
break
}
}