channel和select
channel
Channel是基于有锁队列实现数据在不同协程之间传输的通道,本质上是由buf循环队列、sendq待发送者队列、recvq待接收者队列三个FIFO队列组成的用于协程之间传输数据的协程安全的通道,sendq和recvq可以认为不限大小。
使用
声明一个通道类型的变量是需要使用make()函数初始化
var ch chan int
ch = make(chan int) // 无缓冲通道
ch = make(chan int, 0) // 无缓冲通道
ch = make(chan int, 3) // 容量为3有缓冲通道
无缓冲通道也被称为同步通道,使用无缓冲通道进行通信的 goroutine 会同步发送和接收。
有缓冲通道当通道内元素数达到最大容量后,向通道执行发送操作就会阻塞。
通道一共有 发送、接收、关闭三种操作
// 发送
ch <- 10 // 将10发送到ch种
// 接收
value := <- ch // 从ch种接收值并赋值给x变量
value, ok := <- ch // ok:通道ch关闭时返回false,没有关闭返回true
<- ch // 从ch中接收值,忽略结果
// 关闭
close(ch)
数据结构
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.
lock mutex
}
type waitq struct {
first *sudog
last *sudog
}
type sudog struct {
g *g
next *sudog
prev *sudog
...
}
sendq和recvq存储了当前Channel由于缓冲区空间不足而阻塞的Goroutine列表,waitq
是一个双向链表,sudog
为等待队列中的一个goroutine。
select
select是Go在语言层面提供的多路IO复用的机制,用来检测多个channel是否准备完毕:可读或可写。
select {
case <-ch1:
// ...
case x := <-ch2:
// ...use x...
case ch3 <- y:
// ...
default:
// ...
}
select的执行有以下情况:
- 没有case,deadlock
- 满足一个case,执行case,否则执行default
- 所有case均无法执行且无default,阻塞
- 满足多个case,随机执行一个case
selectgo函数的执行分为四个步骤:首先,随机生成一个遍历case的轮询顺序 pollorder 并根据 channel 地址生成加锁顺序 lockorder,随机顺序能够避免channel饥饿,保证公平性,加锁顺序能够避免死锁;然后,根据 pollorder 的顺序查找 scases 是否有可以立即收发的channel,如果有则获取case索引进行处理;再次,如果pollorder顺序上没有可以直接处理的case,则将当前 goroutine 加入各 case 的 channel 对应的收发队列上并等待其他 goroutine 的唤醒;最后,当调度器唤醒当前 goroutine 时,会再次按照 lockorder 遍历所有的case,从中查找需要被处理的case索引进行读写处理,同时从所有case的发送接收队列中移除掉当前goroutine