这主要是由于 channel 的内部实现机制,channel 的 send 或 recv 操作在发现有等待中的接收器( chan.recvq )或发送器( chan.sendq )时,会直接把值交给它,并向其设置为下一个要唤醒的 goroutine 。然后在循环中再次的操作,才会阻塞,阻塞时自己也会在相应的等待队列中。所以每次的 send 或 recv 操作,都会执行两次,输出的形式也是连着输出,但输出和访问 send 和 recv 函数的顺序是不一样的,因为存在阻塞。
这个是比较直观一些的代码,因为调度不一致,所以我用 runtime.Gosched() 方法保证一定是 recv 的 goroutine 先运行。
https://play.golang.org/p/wmU0fpTt5uf这里是带有 Go Channel 源码部分的一些打印:
------------- call unbuffered channel recv ---------------- # 接收器先启动,调用 recv 时阻塞并将自己放入队列
------------- call unbuffered channel send ---------------- # 发送器启动,调用 send,并尝试获取
------------- unbuffered channel send ---------------- # 发现队列中有可用的接收器,尝试直接发送
------------- unbuffered channel sent ---------------- # 成功
written0 #继续向下执行,打印 witten
------------- call unbuffered channel send ---------------- # for 执行到第二次,调用 send,阻塞了
received # 发送器在发送时,直接修改了接收器的执行栈,所以接收器不需要从阻塞那里再次运行,而是得到值,并继续向下执行,也就是打印
------------- call unbuffered channel recv ---------------- # for 执行到第二次,调用 recv
------------- unbuffered channel receive ---------------- # 发现队列中有可用的发送器,所以直接取值
------------- unbuffered channel received ---------------- # 成功
received # 继续向下执行,打印 received
------------- call unbuffered channel recv ---------------- # 再次调用 recv,但此时队列已经空了,所以阻塞
written1 # 此处的打印是上面被阻塞的 send 的向下执行,同样发送器也不需要在阻塞处继续执行,而是向下执行,所以直接打印第二次循环的输出
------------- call unbuffered channel send ---------------- # 下面就不用详细讲了,都是一样的流程
------------- unbuffered channel send ----------------
------------- unbuffered channel sent ----------------
written2
------------- call unbuffered channel send ----------------
received
------------- call unbuffered channel recv ----------------
------------- unbuffered channel receive ----------------
------------- unbuffered channel received ----------------
received
------------- call unbuffered channel recv ----------------
written3
------------- call unbuffered channel send ----------------
------------- unbuffered channel send ----------------
------------- unbuffered channel sent ----------------
written4
received
------------- call unbuffered channel recv ----------------
如果想看源码来调试或了解,推荐看这里: https://golang.design/under-the-hood/zh-cn/part1basic/ch03lang/chan/