01
介绍
Golang 语言社区流传一句口号:
Do not communicate by sharing memory; instead, share memory by communicating.
安全访问共享变量是并发编程的一个难点,在 Golang 语言中,倡导通过通信共享内存,实际上就是使用 channel 传递共享变量,在任何给定时间,只有一个 goroutine 可以访问该变量的值,从而避免发生数据竞争。
关于 channel 的使用方法,我们在之前的文章中「Go 语言学习之 goroutine 和 channel」介绍过,本文我们从 channel 的数据结构和执行逻辑两个方面介绍一下它的实现原理。
02
数据结构
我们看一下 Golang 源码中 channel 的数据结构。
$GOROOT/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 mutex
}
阅读 channel 的源码,可以发现 channel 的数据结构是 hchan 结构体,包含以下字段:
通过阅读 channel 的数据结构,可以发现 channel 是使用环形队列作为 channel 的缓冲区,datasize
环形队列的长度是在创建 channel 时指定的,通过 sendx
和 recvx
两个字段分别表示环形队列的队尾和队首,其中,sendx 表示数据写入的位置,recvx 表示数据读取的位置。
字段 recvq 和 sendq 分别表示等待接收的协程队列和等待发送的协程队列,当 channel 缓冲区为空或无缓冲区时,当前协程会被阻塞,分别加入到 recvq 和 sendq 协程队列中,等待其它协程操作 channel 时被唤醒。其中,读阻塞的协程被写协程唤醒,写阻塞的协程被读协程唤醒。
字段 elemtype 和 elemsize 表示 channel 中元素的类型和大小,需要注意的是,一个 channel 只能传递一种类型的值,如果需要传递任意类型的数据,可以使用 interface{}
类型。
字段 lock 是保证同一时间只有一个协程读写 channel。
03
执行逻辑
在 Golang 语言中,可以对 channel 进行读写操作,本小节我们分别介绍一下 channel 的读操作和写操作。
写操作 channel,分为两种情况,第一种是 channel 的缓冲区未写满,直接将数据写入缓冲区,结束 send 操作;第二种是 channel 的缓冲区已写满,此时,当前操作 channel 的协程将会被加入 sendq 等待发送的协程队列,等待被读协程唤醒。
需要注意的是,当 recvq 队列不为空时,证明缓冲区没有数据,但是有协程等待读取数据,此时,数据将不再写入缓冲区,而是会直接把数据传递给 recvq 队列中的第一个协程。
读操作 channel,也分为两种情况,第一种是 channel 的缓冲区中有数据,直接读取缓冲区中的数据,结束 recv 操作;第二种是 channel 的缓冲区中没有数据,此时,当前操作 channel 的协程加入 recvq 等待接收的协程队列,等待被写协程唤醒。
需要注意的是,当 sendq 队列不为空时,并且缓冲区已写满,此时,将直接从 sendq 队列中的第一个协程读取数据。
当 channel 被关闭时,recvq 和 sendq 中的所有协程被唤醒,其中 recvq 中的协程读取到的数据全部是 nil,sendq 中的协程会触发 panic。
04
总结
本文我们在 channel 的数据结构和执行逻辑两个方面介绍了 channel 的实现原理,其中,执行逻辑小节中,重点介绍了 channel 的读写操作。如果读者朋友们想要了解创建 channel 的执行逻辑,可以阅读源码中的函数 func makechan(t *chantype, size int) *hchan
。
参考资料: https://golang.org/doc/effective_go#sharing https://blog.golang.org/codelab-share