Go语言channel的输出顺序
23 Dec 2022 -
关于有缓冲和无缓冲channel,网上有许多介绍。
简单来讲,有缓冲的channel提供了一个有限长度的有序通知队列,生产者在队列充满前都是无阻塞的向里面填入内容,
且当队列非空时消费者可以无阻塞的从channel中接收通知,队列空时消费者阻塞。
而无缓冲的channel提供的是阻塞的通知机制,生产者在向其中填入内容时可能会阻塞,
阻塞的条件是消费者此时是否已经阻塞在channel上等待接收,如果没有则生产者阻塞。
但是,关于无缓冲channel的通知顺序,网上没有太多介绍。
本文通过简单的实验确认了,无缓冲channel的通知顺序同样是按照生产者发出通知的顺序来的。
不过如果生产者本身发起通知的顺序不定,则channel的输出顺序也会不定。
1 生产者按序写入channel
实验代码如下。这段代码对一个无缓冲channel中按照每100ms的间隔向里面写入,标号顺序为0~9。
在等待足够长的时间之后(2s),再反复从channel取出标号。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
for i := 0; i < 10; i++ {
go func(idx int) {
ch <- idx
}(i)
time.Sleep(time.Millisecond * 100)
}
time.Sleep(time.Second * 2)
for i := 0; i < 10; i++ {
fmt.Printf("%v\n", <-ch)
}
}
反复运行,可以看到总是有如下输出:
$ go run main.go
0
1
2
3
4
5
6
7
8
9
说明无缓冲channel的通知顺序同样是按照生产者发出通知的顺序来的。
2 生产者无序写入channel
使用强制睡眠并不能反映真实代码中常见的多goroutine竞争channel的场景。
于是,去掉上述代码的部门睡眠,得到如下代码:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
for i := 0; i < 10; i++ {
go func(idx int) {
ch <- idx
}(i)
}
time.Sleep(time.Second * 2)
for i := 0; i < 10; i++ {
fmt.Printf("%v\n", <-ch)
}
}
运行可以发现,输出的顺序并没有按照0~9的顺序来。
由于goroutine的启动与调度顺序并不完全按照代码调用顺序来,所以channel的输出也不会按照0~9的顺序出现。
实际打印出的顺序就是生产者goroutine中写入channel的顺序。
多次运行,其中两次的结果如下:
$ go run main.go
3
0
1
2
6
4
5
7
8
9
$ go run main.go
9
0
1
2
3
4
5
6
7
8
本文通过代码实验了channel的输出顺序,确定了channel具有保序的特性。