面试题答案
一键面试Go语言通道的底层数据结构
Go语言的通道(channel)底层数据结构主要包含以下几个部分:
- hchan结构体:这是通道的核心数据结构,定义在
runtime/chan.go
中。它包含了通道的状态信息、缓冲区、发送和接收队列等。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 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 }
- 缓冲区:如果通道是带缓冲的,
buf
指针指向一块连续的内存区域,用于暂存数据。qcount
表示当前缓冲区中的元素数量,dataqsiz
表示缓冲区的大小。 - 发送和接收队列:
sendq
和recvq
是两个双向链表,用于存储因通道满或空而阻塞的协程(sudog
结构体)。sudog
结构体包含了协程的信息以及与通道交互的相关数据。
向已满的缓冲通道发送数据时的底层操作
- 获取锁:首先,发送操作会获取通道的
lock
,以保证对通道数据结构的操作是线程安全的。 - 检查通道状态:
- 如果通道已关闭,会触发
panic
,提示向已关闭的通道发送数据。 - 如果缓冲区已满(
qcount == dataqsiz
),则将当前协程封装成一个sudog
结构体,并将其加入到sendq
发送等待队列中。
- 如果通道已关闭,会触发
- 阻塞当前协程:当前协程会被阻塞,让出CPU,进入睡眠状态,等待被唤醒。
- 调度器调度:Go语言的调度器(
runtime
)会将这个协程从运行队列中移除,并调度其他可运行的协程。 - 被唤醒:当缓冲区有空间时(例如有其他协程从通道接收数据),调度器会从
sendq
队列中取出一个sudog
,唤醒对应的协程。该协程重新获取锁,将数据发送到缓冲区的合适位置(根据sendx
索引),然后更新sendx
和qcount
,最后释放锁。
从一个空的通道接收数据时的操作
- 获取锁:同样,接收操作首先获取通道的
lock
。 - 检查通道状态:
- 如果通道已关闭且缓冲区为空,接收操作立即返回,返回值为通道元素类型的零值。
- 如果缓冲区为空,将当前协程封装成一个
sudog
结构体,并将其加入到recvq
接收等待队列中。
- 阻塞当前协程:当前协程被阻塞,进入睡眠状态,等待被唤醒。
- 调度器调度:调度器将该协程从运行队列中移除,调度其他可运行的协程。
- 被唤醒:当缓冲区有数据时(例如有其他协程向通道发送数据),调度器从
recvq
队列中取出一个sudog
,唤醒对应的协程。该协程重新获取锁,从缓冲区的合适位置(根据recvx
索引)接收数据,然后更新recvx
和qcount
,最后释放锁。