golang:当涉及多个频道时,选择如何工作?

Ter*_*ang 8 select scheduling channel go goroutine

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

select {
case <- chana:
case <- chanb:
}
Run Code Online (Sandbox Code Playgroud)

即使两个通道都有数据,但在处理此选择时,如果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()
}
Run Code Online (Sandbox Code Playgroud)

运行此演示,当其中一个chana,chanb完成读/写,另一个可能仍然保留999-1.

有没有办法确保平衡?

找到相关主题
golang-channels-select-statement

icz*_*cza 12

Go select语句不偏向于任何(准备好的)案例.引用规范:

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

如果可以进行多次通信,则随机选择一个.这不是一个完美的随机分布,并且规范不保证,但它是随机的.

你所经历的是Go Playground的结果GOMAXPROCS=1(你可以在这里验证)和goroutine调度程序没有先发制人.这意味着默认情况下goroutines不是并行执行的.如果遇到阻塞操作(例如从网络读取,或尝试从阻塞的通道接收或发送),则将goroutine置于驻留状态,并且另一个准备继续运行.

由于您的代码中没有阻塞操作,因此goroutine可能不会被置于停放状态,并且可能只有一个"生产者"goroutine将运行,而另一个可能无法安排(永远).

在我的本地计算机上运行您的代码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
Run Code Online (Sandbox Code Playgroud)

如果您需要确定单个案例的优先级,请查看以下答案:强制执行go select语句的优先级

默认行为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
    }
}
Run Code Online (Sandbox Code Playgroud)

如果两个电源值都相同,则上述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
    }
}
Run Code Online (Sandbox Code Playgroud)

有关goroutine调度的更多详细信息,请参阅以下问题:

Go运行时使用的线程数

Goroutines 8kb和Windows操作系统线程1 MB

当在golang中写入文件时阻塞了许多goroutine时,为什么不创建多个线程?