channel是golang中重要的组成部分。也是协程通信间通信的方式
channel常见理论面试题
channel 什么情况下会出现panic
- 试图往已close的channel继续发送
- 试图关闭已关闭的channel
- 试图关闭一个nil的channel
向为nil的channel发送数据会怎样
给定一个 nil channel c:
<-c 从 c 接收将永远阻塞
c <- v 发送值到 c 会永远阻塞
channel close后读的问题
关闭后继续读数据,已在缓冲区内的可以读取到数据,而后得到的是零值(对于int,就是0)。
我们可以通过for循环遍历channel,来获取到已经写入的值
|
|
对于可能获取到零值,我们可以使用断言判断,ok为false代表channel已关闭后读取的
|
|
也可以在close channel之后有意将channel 置为 nil, 如此再使用,并不会读取到零值
对于不带缓冲的ch,和带缓冲的一样,channel close掉之后并不影响读,只影响写入
channel底层源码剖析
go的运行时源码在runtime/chan.go
文件下,可以在该文件下查看具体的数据结构,send(channel <-),receive(-> channel),close channel等操作
数据结构
|
|
go的channel底层结构包含三部分:
- 与buffer相关的属性字段,qcount, dataqsiz,buf。带缓冲区的channel会使用到这些字段,buffer中存放了待接收的数据。使用ring buffer实现
- waitq相关的属性,可以理解为是一个 FIFO 的标准队列。其中 recvq 中是正在等待接收数据的 goroutine,sendq 中是等待发送数据的 goroutine。waitq 使用双向链表实现。
- 其他属性,全局互斥锁lock,用于保证线程安全、elemtype(元素类型)、closed(通道关闭状态)等
往通道发送数据 send to channel
整体发送流程
- 如果是nil channel,则直接阻塞
- chan已经被close,如果继续发送,则报panic
- 从接收队列中出队一个等待的receiver,发送的元素正好有goroutine等待,则直接将数据交给该goroutine;
调用send函数,递增sendx,recvx的索引,然后直接把元素给到等他的goroutine,并且唤醒他; - 如果buffer缓存区未满时,ring buffer 还有空间,那么把元素放入buffer,递增索引并返回
- 如果buffer缓存区已满,发送者的goroutine就会加入到发送者的等待队列sendq中,直至被唤醒。 如果数据已被取走,或channel被close, 会阻塞当前goroutine,保存好goroutine当前状态,等待被唤醒;
发送流程源码
|
|
往通道接受数据 receive from channel
整体接收流程
- 如果是nil channel, 则永久阻塞
- 如果通道已close,且通道缓冲没有元素时候,直接返回
- 从发送队列sendq中取出一个发送者sender,发送者不为空时候,将发送者数据传递给该goroutine
- buffer缓冲队列中有数据情况下,从缓存队列取出数据,传递给当前goroutine(接收者)
receive源码
|
|
关闭channel
整体流程
- nil的channel关闭直接panic
- closed的channel关闭直接panic
- 遍历所有的接受队列与发送队列,并依次唤醒对应的goroutine
源码
|
|