当涉及多个频道时,select如何工作?

当涉及多个频道时,select如何工作?,select,go,scheduling,channel,goroutine,Select,Go,Scheduling,Channel,Goroutine,我发现在多个非缓冲通道上使用select时 select { case <- chana: case <- chanb: } 选择{ 案例如评论中所述,如果您想确保平衡,您可以放弃在reading goroutine中使用选择,并依赖无缓冲通道提供的同步: go func() { for { <-chana acount++ <-chanb bcount++ if acount =

我发现在多个非缓冲通道上使用select时

select {
case <- chana:
case <- chanb:
}
选择{

案例如评论中所述,如果您想确保平衡,您可以放弃在reading goroutine中使用
选择
,并依赖无缓冲通道提供的同步:

go func() {
    for {
        <-chana
        acount++
        <-chanb
        bcount++

        if acount == 1000 || bcount == 1000 {
            fmt.Println("finish one acount, bcount", acount, bcount)
            break
        }
    }
    wg.Done()
}()
go func(){
为了{

编辑:你也可以从供应端取得平衡,但@icza的回答似乎比这更好&还解释了最初导致这种情况的调度。令人惊讶的是,即使在我的(虚拟)机器上,它也是片面的

这里有一些东西可以从供给端平衡两个程序(在操场上似乎不起作用)

主程序包
进口(
“fmt”
_“net/http/pprof”
“同步”
“同步/原子”
“时间”
)
func main(){
chana:=制造(成龙国际)
通道:=制造(通道内部)
无功平衡开关int32
go func(){
对于i:=0;i<1000;i++{
对于原子.LoadInt32(&balanceSwitch)!=0{
fmt.Println(“持有R1”)
时间。睡眠(时间。纳秒*1)
}
chanaGo声明不偏向任何(现成)案例。引用规范:

如果一个或多个通信可以继续,则通过统一伪随机选择选择一个可以继续的通信。否则,如果存在默认情况,则选择该情况。如果没有默认情况,则“select”语句将阻塞,直到至少一个通信可以继续

如果可以进行多个通信,则随机选择一个。这不是一个完美的随机分布,规范也不能保证这一点,但它是随机的

您所体验到的是Go Playerd具有
GOMAXPROCS=1
()的结果,并且goroutine调度程序不是抢占式的。这意味着默认情况下goroutine不会并行执行。如果遇到阻塞操作,goroutine将被置于停止状态(例如,从网络读取,或尝试从阻塞的通道接收或发送),另一个准备运行的通道继续

由于您的代码中没有阻塞操作,goroutines可能不会被放置在公园中,并且可能只有一个“producer”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
如果您需要确定单个案例的优先级,请查看以下答案:

select
的默认行为并不保证优先级相等,但平均而言,它将接近于此。如果您需要保证优先级相等,则不应使用
select
,但您可以从两个通道执行一系列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
    }
}
有关goroutine日程安排的更多详细信息,请参阅以下问题:


select
不保证它会选择哪个频道。
select
与“余额”相反事实上。如果您想平均分割-从一个通道读取,然后从另一个通道读取,而不选择
,则选择
。如果您的任何goroutine确实在做任何工作,您在公平性方面不会有任何问题。@zerkms是的,在
运行时中找到。选择go
,它确实会使用
fastrand
.Vir通过排序结果检查每个案例实际机器通常分配1个VCPU,这导致
GOMAXPROCS
默认为1,这将解释您所经历的“单边”效应。您可以使用
fmt.Println(runtime.GOMAXPROCS(0))进行验证
code.Ok。我试过上面的代码。实际上,我分配了两个VCPU。在我的系统上它看起来仍然是片面的。这些是一个帐户的数字,b帐户:19 1000、243 1000、1000 1、1000 38、1000 635、262 1000、1 1000、1000 51、1 1000、1000 98、1 1000、245 1000、1 1000,因为示例中有3个goroutine在工作(2个生产者和1个消费者),只要可用的CPU少于3个,副作用就可以用同样的理由来解释。在我的例子中,我有4个CPU,所以这是没有经验的。对于{}来说,具有同等优先级的例子是否会浪费更多的CPU时间进行
的空迭代
循环,通过通道的数据更稀疏?因此基本上一个goroutine总是使用一个CPU 1)检查通道-空2)选择默认值3)检查chanb-empty 4)选择默认值5)评估if-false。一遍又一遍地与其他goroutine共享该CPU来做有用的工作?@aiguy Nice catch!你说得对,谢谢你指出。我也为这种情况添加了一个解决方案。请参阅编辑后的答案。@icza如果我从两个通道
ch1
ch2中选择
和两者都有元素要同时读取。go例程将选择一个案例执行,比如
ch2
。我的问题是:
ch1
中的可用元素是否会被拉出?@MạnhQuyế特恩盖伊ễ不,不会,只执行所选的通信操作。
finish one acount, bcount 1000 901
finish one acount, bcount 1000 335
finish one acount, bcount 1000 872
finish one acount, bcount 427 1000
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
    }
}
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
    }
}