硬核——golang之channel、select
在日常开发中,数据结构channel和select语句被高频使用,本文基于Go1.18.1版本的源码,探讨channel的底层数据结构和select访问Channel在编译期和运行时的底层原理 探讨底层原理是一个很奇妙却枯燥乏味的过程,希望读者您能保持足够的耐心,我们开始吧🤗 channel 的底层数据结构 1 ch := make(chan it, 5) 我们通过make关键字创建了一个缓冲区为5存储数据类型为int的channel ch存储在栈上的一个指针,而指向的是堆上的hchan结构体 首先一个channel需要能支持多个goroutine并发访问,这需要一把🔒(lock mutex) 对于有缓冲区的channel而言,需要知道缓冲区的位置(buf unsafe.Pointer)以及缓冲区内有多少个元素(qcount unit),每个元素多大(datasiz uint),所以缓冲区实际上就是一个数组 因为golang运行时中内存复制、垃圾回收等机制依赖数据的类型信息,所以还需要一个指针(elemtype *_type)指向数据的类型元数据 为了支持定时器的功能添加了timer *timer channel支持交替的读写,需要分别记录读和写下标的位置(sendx uint recvx uint),当读和写不能立即完成的时候,需要能够让当前的goroutine在channel上等待,当条件满足时,需要可以立即唤醒等待中的goroutine,所有需要两个等待队列来针对读和写操作(sendq waitq recvq waitq) channel支持被关闭(closed uint32) 综上所述,channel的底层数据结构就长这个样子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 timer *timer // timer feeding this chan 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. // // Do not change another G's status while holding this lock // (in particular, do not ready a G), as this can deadlock // with stack shrinking. lock mutex } 当我们创建一个有缓冲区的channel的时候,recvx和sendx都为0,不断往channel中发送数据的时候,因为没有goroutine在等待接收或发送数据,所以send会不断向后移动,最后移动回起点(recvx = sendx),那么这个时候则表明channel的缓冲区满了,其实channel的缓冲区是一个环形队列,也称之为环形缓冲区 ...