这是一篇后端实践选题,分析GOLANG的通道特点
在GO语言入门指南-chan | 青训营 中讲了通道的用法和分类,这篇文章补充一下select的用法,以及通道的一些底层原理。
select
select有点类似switch,但只能配合通道使用。
在没有执行某条case之前,select会一直检查是否有通道收到消息,如果同时有多个case满足,则随机执行一条。
使用代码如下所示:
select {
case <- ch1:
fmt.Println("case 1")
case <- ch2:
fmt.Println("case 2")
default:
fmt.Println("default")
}
当然,和switch类似,select也可以设置default,当没有case满足时,直接执行default结束select。
chan底层结构
在golang的src/runtime/chan.go中有通道的源码,在这里可以看到chan结构体的组成
type hchan struct {
qcount uint
dataqsiz uint
buf unsafe.Pointer
elemsize uint16
closed uint32
elemtype *_type
sendx uint
recvx uint
recvq waitq
sendq waitq
lock mutex
}
简单来说,chan就是一个带锁的环形队列。
lock用于确保chan可以处理并发场景
qcount记录环形队列中的数据数量
dataqsiz记录数据大小
buf是指向真正存储数据的数组的指针
elemsize记录通道元素的大小
closed记录通道是否关闭
elemtype记录通道元素的类型
sendx、recvx分别记录环形队列中的入队索引和出队索引
recvq、sendq分别记录写通道阻塞的goruntine和读通道阻塞的goruntine
在读写操作通道阻塞时,goruntine会被打包成sudog结构体放到这两个队列中,等待通道唤醒。
可以看到,recvq和sendq的数据类型是waitq,这个结构体的结构如下:
type waitq struct {
first *sudog
last *sudog
}
其实就是一个存储sudog的队列,存放了队列的队首和队尾指针,sudog中不仅包含了goruntine的信息,还有指向前后sudog的指针,构成一个双向链表。
以上只是看源码过程中的一些简单理解,如果有不对的地方,欢迎批评指正!