深入理解 Go 语言并发编程--管道(channel) 的底层原理
管道是 Go 语言协程间通信的一种常用手段,管道的读写操作也有可能会阻塞用户协程,也就是说有可能会切换到调度器。协程因为管道而阻塞时,只有当其他协程再次读或者写管道时,才有可能解除这个协程的阻塞状态。
1. 管道的基本用法
管道是 Go 语言协程间通信的一种常用手段,可以分为无缓冲管道和有缓冲管道。因为无缓冲管道本身没有容量,不能缓存数据,所以只有当协程在等待读时,写操作才不会阻塞协程;或者当有协程在等待写时,读操作才不会阻塞协程。因为有缓冲管道本身有一定容量,可以缓存一定数据,所以当协跑第一执行写操作时,即使没有其他协程在等待读,只要管道还有剩余容量,写操作就不会阻塞协程;或者当协程执行读操作时,即使没有其他协程在等待写,只要管道还有剩余数据,读操作就不会阻塞协程。
下面写一个简单的 Go 程序,学习管道的基本用法,代码如下所示:
package main
import (
"fmt"
"time"
)
func main() {
queue := make(chan int, 1)
go func() {
for {
data := <-queue //读取
fmt.Print(data, "") //0 1 2 3 4 5 6 7 8 9
}
}()
for i := 0; i < 10; i++ {
queue <- i //写入
}
time.Sleep(time.Second)
}
参考上面代码,主协程循环向管道写入整数,子协程循环从管道读取数据。主协程休眠 1s 是为了防止主协程结束,整个 Go 程序退出,导致子协程也提前结束。函数 make 用于初始化 Go 语言的一些内置类型,如切片 slice、散列列 map 以及管道 chan。注意用函数 make 初始化时,第一个参数 chan int 表示管道只能用来传递整型数据,第二个参数表示管道的容量是 1,即最多只能缓存一个整型数据。
管道的操作还是比较简单的,无非就是读、写以及关闭操作。这里提出一个问题,如果程序没有初始化管道,却执行读或者写操作会发生什么呢?或者说,如果一个管道已经被关闭了,这时候执行读或者写操作会发生什么呢?我们写一些简单的 Go 程序测试一下。
第 1 个程序:不初始化管道,直接