go select 原理

编译器会使用如下的流程处理 select 语句:

  1. 将所有的 case 转换成包含 channel 以及类型等信息的 runtime.scase 结构体。
  2. 调用运行时函数 runtime.selectgo 从多个准备就绪的 channel 中选择一个可执行的 runtime.scase 结构体。
  3. 通过 for 循环生成一组 if 语句,在语句中判断自己是不是被选中的 case。
    一个包含三个 case 的正常 select 语句会被展开成以下逻辑:
selv := [3]scase{}
order := [6]uint16
for i, cas := range cases {
    c := scase{}
    c.kind = ...
    c.elem = ...
    c.c = ...
}
chosen, revcOK := selectgo(selv, order, 3)
if chosen == 0 {
    ...break
}
if chosen == 1 {
    ...break
}
if chosen == 2 {
    ...break
}

最重要的是 selectgo 这个函数:

  1. 执行必要的初始化操作并决定处理 case 的两个顺序:轮询顺序 pollOrder 和加锁顺序 lockOrder。
  • 轮询顺序:通过 runtime.fastrandn 函数引入随机性。
  • 加锁顺序:按照 Channel 的地址排序后确定加锁顺序。

随机的轮询顺序可以避免 channel 的饥饿问题,保证公平性。而根据 channel 的地址顺序确定加锁顺序能够避免死锁的发生。

  1. 按照加锁顺序 lockOrder锁住所有 channel。
  2. 遍历所有的 channel,查看其是否可读或者可写。如果其中的 channel 可读或者可写,则解锁所有 channel,并返回对应的 channel 数据。
  3. 假如没有 channel 可读或者可写,但是有 default 语句,则返回 default 语句对应的 scase 并解锁所有的channel。
  4. 假如既没有 channel 可读或者可写,也没有 default 语句,则将当前运行的 groutine 阻塞,并加入到当前所有channel的等待队列中,然后解锁所有 channel,等待被唤醒。
  5. select 中一些 channel 可读或者可写了,当前 goroutine 被唤醒,并再次加锁所有channel。遍历所有channel找到那个对应的 channel 和 sudog,唤醒 sudog,并将没有成功的 sudog 从所有 channel 的等待队列中移除。
  6. 如果对应的scase值不为空,则返回需要的值,并解锁所有channel。如果对应的scase为空,则循环此过程。

select 和 channel 的关系:
在这里插入图片描述

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值