Go Channel
读写nil管道均会阻塞 关闭的管道仍然可以读取数据 向关闭的管道写数据会触发panic
只有一个缓冲区的管道,写入数据 —> 加锁; 读出数据 -> 解锁
特性⌗
初始化⌗
- 变量声明
var ch chan int declare chan, value == nil
- make()
ch1 := make(chan string) no-buffered chan
ch2 := make(chan string, 5) buffered chan
管道操作⌗
- 操作符: <- -> 默认为双向可读写,在函数传递间可使用操作符限制读写
func ChanParamR(ch <-chan int) {
only can read from chan
}
func ChanParamW(ch chan<- int) {
only can write to chan
}
- 数据读写 协程读取管道时,阻塞的条件有:
- chan no-buffer
- chan buffer no data
- chan value == nil 协程写入管道时,阻塞的条件有:
- chan no-buffer
- chan buffer is full
- chan value == nil
实现原理⌗
数据结构⌗
https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/runtime/chan.go
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
-
环形队列 chan内部实现了一个环形队列,队列长度在chan创建时指定 sendx: 队尾, 写入位 recvx: 队首, 读取位
-
等待队列
- goroutine从chan读 -> buf为空或没有buf -> 当前goroutine阻塞 -> 加入recvq
- goroutine向chan写 -> buf已满或没有buf -> 当前goroutine阻塞 -> 加入sendq 处于等待队列中的协程会在其他协程操作管道时被唤醒:
- 因读阻塞的协程会被向管道写入的协程唤醒
- 因写阻塞的协程会被从管道读取的协程唤醒
Invariants:
At least one of c.sendq and c.recvq is empty,
except for the case of an unbuffered channel with a single goroutine
blocked on it for both sending and receiving using a select statement,
in which case the length of c.sendq and c.recvq is limited only by the
size of the select statement.
For buffered channels, also:
c.qcount > 0 implies that c.recvq is empty.
c.qcount < c.dataqsiz implies that c.sendq is empty.
- 类型信息
- 一个管道只能传递一种类型的值
- 如果需要管道传递任意类型的数据,可以使用interface{}类型
- 互斥锁 一个管道同时仅允许被一个协程读写
管道操作⌗
-
创建管道 创建管道 -> 初始化hchan结构
-
写入管道 trick: 当接收队列recvq不为空时,说明缓冲区中没有数据但有协程在等待数据 会把数据直接传递给recvq队列中的第一个协程,而不必再写入缓冲区
-
读出管道 trick: 当等待发送队列sendq不为空,且没有缓冲区, 那么此时将直接从sendq队列的第一个协程中获取数据
-
关闭管道 关闭管道时会把recvq中的协程全部唤醒, 协程会获取对应类型的零值 同时会把sendq队列中的协程全部唤醒,协程会触发panic
会触发panic的操作还有:
- 关闭值为nil的管道
- 关闭已经被关闭的管道
- 向已经关闭的管道写入数据
常见用法⌗
-
单向管道
-
select 使用select可以监控多个管道 select的case语句读管道时不会阻塞
-
for-range for-range可以持续从管道中读出数据,当管道中没有数据时会阻塞当前协程
Read other posts