Go: Select 语句的执行顺序

yixiao9206 · 2020-05-31 20:11:27 · 2773 次点击 · 预计阅读时间 4 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2020-05-31 20:11:27 的文章,其中的信息可能已经有所发展或是发生改变。

本文基于 Go 1.14

select 允许在一个 goroutine 中管理多个 channel。但是,当所有 channel 同时就绪的时候,go 需要在其中选择一个执行。此外,go 还需要处理没有 channel 就绪的情况,我们先从就绪的 channel 开始。

顺序

select 不会按照任何规则或者优先级选择就绪的 channel。go 标准库在每次执行的时候,都会将他们顺序打乱,也就是说不能保证任何顺序。

看一个有三个就绪的 channel 的例子:

func main() {
    a := make(chan bool, 100)
    b := make(chan bool, 100)
    c := make(chan bool, 100)
    for i := 0; i < 10; i++ {
        a <- true
        b <- true
        c <- true
    }
    for i := 0; i < 10; i++ {
        select {
        case <-a:
            print("< a")

        case <-b:
            print("< b")

        case <-c:
            print("< c")

        default:
            print("< default")
        }
    }
}

这三个 channel 的缓冲区都填满了,使得 select 选择时不会堵塞。下面是程序的输出:

< b< a< a< b< c< c< c< a< b< b

在 select 的每次迭代中,case 都会被打乱:

由于 go 不会删除重复的 channel,所以可以使用多次添加 case 来影响结果,代码如下:

func main() {
    a := make(chan bool, 100)
    b := make(chan bool, 100)
    c := make(chan bool, 100)
    for i := 0; i < 10; i++ {
        a <- true
        b <- true
        c <- true
    }
    for i := 0; i < 10; i++ {
        select {
        case <-a:
            print("< a")
        case <-a:
            print("< a")
        case <-a:
            print("< a")
        case <-a:
            print("< a")
        case <-a:
            print("< a")
        case <-a:
            print("< a")
        case <-a:
            print("< a")

        case <-b:
            print("< b")

        case <-c:
            print("< c")

        default:
            print("< default")
        }
    }
}

输出的结果:

< c< a< b< a< b< a< a< c< a< a

当所有 channel 同时准备就绪时,有 80%的机会选择通道 a。下面来看一下 channel 未就绪的情况。

没有就绪 channels

select 运行时,如果没有一个 case channel 就绪,那么他就会运行 default:,如果 select 中没有写 default,那么他就进入等待状态,如下面这个例子

func main() {
    a := make(chan bool, 100)
    b := make(chan bool, 100)
    Go func() {
        time.Sleep(time.Minute)
        for i := 0; i < 10; i++ {
            a <- true
            b <- true
        }
    }()

    for i := 0; i < 10; i++ {
        select {
        case <-a:
            print("< a")
        case <-b:
            print("< b")
        }
    }
}

上面那个例子中,将在一分钟后打印结果。select 阻塞在 channel 上。这种情况下,处理 select 的函数将会订阅所有 channel 并且等待,下面是一个 goroutine#7 在 select 中等待的示例,其中另一个 goroutine#4 也在等待 channel:

Goroutine(G7)订阅所有频道并在列表末尾等待。 如果 channel 发送了一条消息,channel 将通知已在等待该消息的另一个 Goroutine。一旦收到通知,select 将取消订阅所有 channel,并且返回到代码运行.

更多关于 channel 与等待队列的信息,请查看作者另外一篇文章Go: 带缓冲和不带缓冲的 Channels

上面介绍的逻辑,都是针对于有两个或者以上的活动的 channel,实际上如果只有一个活动的 channel,Go 乐意简化 select。

简化

如果只有一个 case 加上一个 default,例子:

func main() {
    t:= time.NewTicker(time.Second)
    for   {
        select {
        case <-t.C:
            print("1 second ")
        default:
            print("default branch")
        }
    }
}

这种情况下。Go 会以非阻塞模式读取 channel 的操作替换 select 语句。如果 channel 在缓冲区中没有任何值,或者发送方准备发送消息,将会运行 default。就像下面这张图:

如果没有 default,则 Go 通过阻塞 channel 的操作方式重写 select 语句。


via: https://medium.com/a-journey-with-go/go-ordering-in-select-statements-fd0ff80fd8d6

作者:Vincent Blanchon  译者:yixiao9206  校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2773 次点击  
加入收藏 微博
被以下专栏收入,发现更多相似内容
2 回复  |  直到 2022-05-10 17:16:25
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传