You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言select语句中Channel缓冲区与Pending队列的优先级问题

为什么你的Go select测试结果和源码逻辑看似矛盾?

你误解了select源码中recv分支的具体行为——它并不是在所有情况下都会优先接收pending发送者的值。核心原因在于:只有当缓冲区为空时,才会直接接收发送者的值;而当缓冲区已满时,recv分支的逻辑是先取出缓冲区头部的元素返回给接收者,再把发送者的值放入缓冲区。

一、拆解你的测试场景流程

你的测试代码中发生了这些事:

  1. 创建了大小为3的缓冲channel,填满了1、2、3,此时缓冲区处于满状态;
  2. 启动goroutine尝试发送4,因为缓冲区已满,这个goroutine被挂到channel的sendq等待队列;
  3. 主goroutine执行select接收channel的值。

二、解析recv分支的实际处理逻辑

你贴的select源码只展示了pass1阶段的检查逻辑,但goto recv后的处理代码(你没贴出来)才是关键:

func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
    if c.dataqsiz == 0 {
        // 无缓冲channel:直接把发送者的值复制给接收者
        if ep != nil {
            typedmemmove(c.elemtype, ep, sg.elem)
        }
    } else {
        // 缓冲channel且已满:先取缓冲区头部元素给接收者,再把发送者的值放入缓冲区
        qp := chanbuf(c, c.recvx)
        if ep != nil {
            typedmemmove(c.elemtype, ep, qp) // 复制缓冲区头部的1到你的val变量
        }
        typedmemmove(c.elemtype, qp, sg.elem) // 把发送者的4放入缓冲区
        c.recvx++
        if c.recvx == c.dataqsiz {
            c.recvx = 0
        }
        c.sendx = c.recvx // 缓冲区已满,sendx和recvx指向同一位置
    }
    sg.success = true
    goready(sg, skip+1) // 唤醒发送goroutine,它会继续执行
    unlockf()
}

在你的场景中,缓冲区已满,所以recv分支做了两件核心事:

  • 把缓冲区头部的1复制给你的val变量;
  • 把发送goroutine的4放入缓冲区尾部;
  • 唤醒发送goroutine(此时缓冲区有了一个空位)。

这就是为什么你看到的结果是receive val is 1,而不是预期的4。

三、什么时候才会优先接收pending发送者的值?

只有当缓冲区为空时,recv分支才会直接接收发送者的值。比如修改你的测试代码:

func main() {
    var mark chan int
    c := make(chan int, 3)
    // 缓冲区为空
    go func() {
        c <- 4
    }()
    time.Sleep(time.Millisecond * 10)
    select {
    case val := <-c:
        fmt.Println("receive val is ", val) // 输出4
    case _ = <-mark:
        fmt.Println("block chan")
    }
}

此时缓冲区为空,发送goroutine被挂到sendq,select的recv分支会直接把发送者的4复制给接收者,结果就是4。

四、总结select中caseRecv的优先级逻辑

对于caseRecv,select的pass1阶段会按以下顺序检查:

  1. 如果sendq有等待的发送者:
    • 若缓冲区为空:直接接收发送者的值;
    • 若缓冲区已满:取出缓冲区头部的值返回,再把发送者的值放入缓冲区;
  2. 如果缓冲区有数据:直接取出缓冲区头部的值返回;
  3. 如果channel已关闭:返回零值;
  4. 否则:进入阻塞等待。

这样设计的核心目的是保证缓冲channel的FIFO语义——缓冲区的元素必须按发送顺序被接收,不能被pending的发送者插队。

内容的提问来源于stack exchange,提问作者HuDahai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 20:07:29