channel

数据结构

Channel 的数据结构定义在 runtime/chan.go 中的 hchan 结构体中

  1. hchan 结构体包含以下字段:
    type hchan struct {
        qcount   uint        // 队列中当前元素个数
        dataqsiz uint        // 循环队列的长度
        buf      unsafe.Pointer // 指向循环队列的指针
        elemsize uint16     // 元素大小
        closed   uint32     // 标识 channel 是否已关闭
        elemtype *_type     // 元素类型
        sendx    uint        // 下一个发送元素在 buf 中的索引
        recvx    uint        // 下一个接收元素在 buf 中的索引
        recvq    waitq       // 等待接收数据的 goroutine 队列(双向链表)
        sendq    waitq       // 等待发送数据的 goroutine 队列(双向链表)
        lock     mutex       // 保护 hchan 的互斥锁
    }
    
  2. buf 是一个循环队列,用于存储 channel 接收的数据 sendx 和 recvx 分别记录下一个可以发送和接收的元素在 buf 中的索引位置
  3. recvq 和 sendq 是两个双向链表,分别用于存储等待从 channel 接收数据和等待向 channel 发送数据的 goroutine lock 是一个互斥锁,用于保护 hchan 结构体中的所有字段,确保 channel 的线程安全性
  4. elemsize 和 elemtype 分别记录 channel 中元素的大小和类型,用于数据的拷贝和类型检查
  5. closed 标识 channel 是否已关闭,用于在发送和接收操作时进行检查

底层原理

Channel 本质上是由三个 FIFO(先进先出)队列组成的用于协程之间传输数据的协程安全的通道 这三个队列分别是: buf 循环队列:存放 channel 接收的数据 sendq 待发送者队列:存放等待发送数据到 channel 的 goroutine 的双向链表 recvq 待接收者队列:存放等待从 channel 接收数据的 goroutine 的双向链表

Channel 的工作原理

  1. 对于无缓冲的 channel,如果没有接收者准备好,发送操作会阻塞发送者 goroutine;如果没有发送者准备好,接收操作会阻塞接收者 goroutine
  2. 对于有缓冲的 channel,在缓冲区未满时发送操作不会阻塞,在缓冲区非空时接收操作不会阻塞。数据先写入缓冲区 buf,然后从缓冲区读取
  3. Channel 传递数据的本质是值拷贝,包括引用类型数据的地址拷贝。数据从发送者栈内存拷贝到缓冲区 buf,或从缓冲区 buf 拷贝到接收者栈内存
  4. 关闭一个 channel 会导致 panic,除非是向一个已经关闭的 channel 发送数据。关闭 channel 后,后续的发送操作会 panic,而接收操作会返回对应类型的零值和 false
  5. 使用无缓冲 channel 时,如果发送者和接收者不能及时配对,可能会导致 goroutine 泄漏,造成内存泄漏。可以通过使用有缓冲 channel 或在发送后关闭 channel 来避免这个问题

综上所述,Golang 的 channel 机制是基于环形队列和双向链表实现的,设计上体现了 Go 并发编程的理念"不要通过共享内存来通信,而是通过通信来共享内存"。开发者需要谨慎使用 channel,特别是无缓冲 channel,以避免出现 goroutine 泄漏等问题。

操作通道状态结果解释
写入到 Channel未初始化panic向未初始化的通道写入数据将导致 panic
未关闭成功/阻塞如果有足够的空间,写入将成功并继续运行;如果通道已满,写入操作将阻塞直到有空间或者通道被关闭;如果通道被关闭,将会 panic
已关闭panic写入操作将 panic
读取 Channel未初始化阻塞从未初始化的通道读取数据将会永远阻塞,直到通道被初始化为止
未关闭成功/阻塞如果有值,读取将成功并返回该值;如果没有值,读取操作将阻塞直到有值或者通道被关闭;如果通道被关闭且为空,返回默认值
已关闭默认值如果有值,读取将成功并返回该值;如果没有值,立即返回默认值;如果通道被关闭且为空,返回默认值
关闭 Channel未初始化尝试关闭未初始化的通道将导致 panic
未关闭关闭通道,后续对通道的读取操作将返回默认值
已关闭panic关闭通道将 panic