Go Channel
Go语言采用CSP模型,让两个独立执行的程序通过消息传递的方式共享内存,Channel就是Golang用来完成消息通讯的数据类型。
结构定义
type hchan struct {
qcount uint // 循环队列中所有数据总数
dataqsiz uint // 循环队列大小,固定的
buf unsafe.Pointer // 指向循环队列的指针
elemsize uint16 // 循环队列中元素的大小
closed uint32 // chan是否关闭
elemtype *_type // 循环队列中元素类型
sendx uint // 已发送元素在循环队列中的位置
recvx uint // 已读取元素在循环队列中的位置
recvq waitq // 等待读取的goroutine队列
sendq waitq // 等待发送的goroutine队列
lock mutex // chan互斥锁
}
type waitq struct {
first *sudog
last *sudog
}
// qcount:chan中已经接收但还没被读取的元素的个数
// buf:用来存放chan接收的数据队列
// sendq 和 recvq 存储了当前 Channel 由于缓冲区空间不足而阻塞的 Goroutine 列表
// 这些等待队列使用双向链表 waitq 表示,链表中所有的元素都是 sudog 结构
分类
- 无缓冲channel
没有定义chan的长度,可看作:"同步模式"
只有在两者都 ready 的情况下,数据才能在两者间传输(实际上就是内存拷贝)。
否则,任意一方先行进行发送或接收操作,都会被挂起,等待另一方的出现才能被唤醒。
- 有缓冲channel
定义的chan有长度,可看作:"异步模式"
在缓冲槽可用的情况下(有剩余容量),发送和接收操作都可以顺利进行。
1.对已经空的chan进行读取时,会导致该goroutine阻塞
2.对已经满的chan进行写入时,会导致该goroutine阻塞
3.写入一个已关闭的chan会报错
4.读取一个已关闭的chan不会报错,chan为空读取为nil
5.关闭一个已关闭的chan会报错
读写
-
初始化
- 根据元素大小、是否含有指针决定存储空间的分配
- 当元素大小为0时,只分配hchan结构体的内存就可以了
- 当没有指针时,连续分配元素大小和结构体大小的内存
- 当存在指针时,需要给指针元素单独分配内存空间
-
写入数据
- 先获取锁
- 判断是否有待接收的协程(recvq中是非空),如果有的话,复制数据给此协程
- 判断是否有空闲缓冲区,如果有的话,把数据复制到缓冲区
- 否则把当前goroutine放入待发送队列(sendq)
- 释放锁
-
读取数据
- 先获取锁
- 有待发送的协程,缓冲区中有数据,从缓冲队首取出数据,从sendq取出G,将G中数据放入缓冲队尾
- 有待发送的协程,缓冲区中没有数据,从sendq取出G,读取G中数据
- 没有待发送的协程,缓冲区中有数据,从缓冲队首取出数据
- 没有待发送的协程,缓冲区中没有数据,将当前的G加入recvq排队,进入睡眠待唤醒
- 释放锁
-
特性
- 通道可以作为参数在函数中传递,当作参数传递时,复制的是引用。
- 通道是并发安全的。
- 同一个通道的发送操作之间是互斥的,必须一个执行完了再执行下一个。接收操作和发送操作一样。
- 缓冲通道的发送操作需要复制元素值,然后在通道内存放一个副本。非缓冲通道则直接复制元素值的副本到接收操作。
- 往通道内复制的元素如果是引用类型,则复制的是引用类型的地址。
-
关闭原则
- 最好在写入端关闭
- 多个写入端,要保证只有一处关闭(可用chan)
-
select
- select就是用来监听和channel有关的IO操作,当 IO 操作发生时,触发相应的动作
-
switch
- 默认每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch, 但是可以使用fallthrough强制执行后面的case代码。
- 加了fallthrough后,会直接运行【紧跟的后一个】case或default语句,不论条件是否满足都会执行