概念及使用场景
通道(channal)是Golang实现CSP并发模型的关键,分为 有缓冲通道 和无缓冲通道。
- 有缓冲管道: channal持有一个固定大小的队列,队列满时发送者将阻塞(反之亦然)。多用于数据共享。
- 无缓冲管道: 发送和接收数据同时完成,如果没有goroutine读取channal,则发送者阻塞(反之亦然)。多用于协程同步。
- 有缓冲+select: +for实现对多个chan的监听操作 +time实现超时控制
基本使用
简单的 无缓冲channal 使用
func main() {
ch := make(chan int) //创建无缓冲管道
go func() {
//发起goroutine,向管道写入数据
ch <- 1
close(ch) //关闭管道
}()
t := <- ch //主goroutine等待读取到管道数据后退出
fmt.Println(t)
return
需要注意的是:
1. 无缓冲在写入时,必须有其他线程在对端接受,否则线程将阻塞。
2. 向关闭的chan写入数据将Panic
3. 可以从关闭的chan中读取数据
4. 重复关闭chann会导致panic
基本结构及内存布局
chan结构体定义在 ./src/runtime/chan.go
文件,type hchan struct
type hchan struct {
qcount uint // 队列数据长度len
dataqsiz uint // 缓冲区长度cap c:=make(chan int, 10)即此值为10
buf unsafe.Pointer // 指向dataqsiz数组的指针
elemsize uint16 //元素类型大小 sizeof(struct)
closed uint32 //关闭标志
elemtype *_type // chan接收的元素类型
sendx uint // 写入chan索引,写入1个数据时,sendx+1
recvx uint // 读取chan索引,被读走1个数据时,recvx+1
recvq waitq // 阻塞在chann上的读等待队列
sendq waitq // 阻塞在chann上的写等待队列
lock mutex //互斥锁 保证hchan的数据读写安全,包括被阻塞的waitq中的sudog
}
waitq类型链表,sudog 为封装的goroutine,包含等待 读/写的被阻塞的goroutine信息。
type waitq struct {
first *sudog
last *sudog
}
type sudog struct {
g *g // 阻塞的goroutine
isSelect bool // select中使用chan是特殊处理的,本章暂时不论
next *sudog
prev *sudog
elem unsafe.Pointer // 数据指针(指向堆内存或栈空间)
acquiretime int64
releasetime int64
ticket uint32
parent *sudog // semaRoot binary tree
waitlink *sudog // g.waiting list or semaRoot
waittail *sudog // semaRoot
c *hchan // channel
}
结构中大约了解下 channal 的实现
-
channal 有缓存时通过读写索引读取数据,并保存了可容纳元素数量(dataqsize) 及当前元素量(qcount)。recvq和sendq为两个队列,遵循先进先出原则,队列元素sudog为封装后的goroutine,包含goroutine等待的chan信息和数据地址,一般为阻塞状态。互斥锁保证hchan中的数据读写安全。
-
对于无缓冲channal,recvq 和 sendq至少有一个为空,且 dataqsiz 和 qcount 都为0。
初始化 channal 过程如下
// use example:
ch := make(chan int, 10)
// code
func makechan(t *chantype, size int) *hchan {
elem := t.elem
// 检查是否存在size越界问题
if size < 0 || uintptr(size) > maxSliceCap(elem.size) || uintptr(size)*elem.size > maxAlloc-hchanSize {
panic(plainError("makechan: size out of range"))
}
var c *hchan
switch {
// make(chan int, 0) size=0 but elem.size=8
// make(struct{}, 2) size=2 but elem.size=0
case size == 0 || elem.size == 0:
//分配hchan大小,无缓冲chan只分配这些,以下是有缓冲chan
c = (*hchan)(mallocgc(hchanSize, nil, true))
// race检测,不懂
c.buf = c.raceaddr()
// channal类型不包含指针
case elem.kind&kindNoPointers != 0:
// 分配空间大小为(hchan结构大小 单个元素大小*请求分配数量)的堆内存
c = (*hchan)(mallocgc(hchanSize+uintptr(size)*elem.size, nil, true))
// c.buf指针指向hchan结构后,即存储元素区
c.buf = add(unsafe.Pointer(c), hchanSize)
// channal类型包含指针
default:
// 分配一块hchan内存