MST
星途 面试题库

面试题:Go语言通道的底层数据结构及操作机制

简述Go语言通道的底层数据结构,当一个协程向一个已满的缓冲通道发送数据时,底层会进行哪些操作?当从一个空的通道接收数据时,又会发生什么?
23.5万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

Go语言通道的底层数据结构

Go语言的通道(channel)底层数据结构主要包含以下几个部分:

  1. 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
    }
    
  2. 缓冲区:如果通道是带缓冲的,buf指针指向一块连续的内存区域,用于暂存数据。qcount表示当前缓冲区中的元素数量,dataqsiz表示缓冲区的大小。
  3. 发送和接收队列sendqrecvq是两个双向链表,用于存储因通道满或空而阻塞的协程(sudog结构体)。sudog结构体包含了协程的信息以及与通道交互的相关数据。

向已满的缓冲通道发送数据时的底层操作

  1. 获取锁:首先,发送操作会获取通道的lock,以保证对通道数据结构的操作是线程安全的。
  2. 检查通道状态
    • 如果通道已关闭,会触发panic,提示向已关闭的通道发送数据。
    • 如果缓冲区已满(qcount == dataqsiz),则将当前协程封装成一个sudog结构体,并将其加入到sendq发送等待队列中。
  3. 阻塞当前协程:当前协程会被阻塞,让出CPU,进入睡眠状态,等待被唤醒。
  4. 调度器调度:Go语言的调度器(runtime)会将这个协程从运行队列中移除,并调度其他可运行的协程。
  5. 被唤醒:当缓冲区有空间时(例如有其他协程从通道接收数据),调度器会从sendq队列中取出一个sudog,唤醒对应的协程。该协程重新获取锁,将数据发送到缓冲区的合适位置(根据sendx索引),然后更新sendxqcount,最后释放锁。

从一个空的通道接收数据时的操作

  1. 获取锁:同样,接收操作首先获取通道的lock
  2. 检查通道状态
    • 如果通道已关闭且缓冲区为空,接收操作立即返回,返回值为通道元素类型的零值。
    • 如果缓冲区为空,将当前协程封装成一个sudog结构体,并将其加入到recvq接收等待队列中。
  3. 阻塞当前协程:当前协程被阻塞,进入睡眠状态,等待被唤醒。
  4. 调度器调度:调度器将该协程从运行队列中移除,调度其他可运行的协程。
  5. 被唤醒:当缓冲区有数据时(例如有其他协程向通道发送数据),调度器从recvq队列中取出一个sudog,唤醒对应的协程。该协程重新获取锁,从缓冲区的合适位置(根据recvx索引)接收数据,然后更新recvxqcount,最后释放锁。