Go原理剖析:Channel数据结构源码解读

256 阅读3分钟

go version go1.22.5

Golang以高性能以及天然的并发的能力,通过轻量级的Goroutine和通道channel,使得编写高并发程序更加容易和安全。

这篇文章来解读Golang当中的Channel

顾名思义,Channel就是一个通信管道,被设计用于实现Goroutine之间的通信。 Go的并发哲学是CSP模型,基于channel实现,CSP的全程是“Communicating Sequential Processes”,这个模型的解释就是:不要通过共享内存来通信,而要通过通信来实现内存共享

Channel的数据结构

channel的功能强大并且复杂,不会是几个字节就能实现的,由一个结构体来承接channel的作用,这个结构体是hchan结构体

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
    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
}
  • qcount : queue(环形数组)的数量
  • dataqsiz: queue(环形数组)的容量
  • buf: 指向存储channel数据的queue(环形数字)的指针
  • elemsize: 元素的大小,大小在计算机系统当中就是字节数
  • closed: channel是否关闭
  • sendx: 下一次写的位置
  • recvx: 下一读的位置
  • sendq:等待发送的queue
  • recvq: 等待接受的queue
  • lock:并发锁,保证channel数据结构的并发安全

image.png

深入理解Channel的数据结构

在创建channel的时候,会有一个环形的数据结构来存储Channel当中的数据,相当于一个buf的缓存,因为他是一个环形的数据结构,所以会有sendxrecvx分别指向下一次写和下一次读,也就是在Channel读写操作的时候提供定位的功能

qcount则是用来表示buf当中的元素,而dataqsiz表示buf的容量

同时Channel和Goroutine是Go并发编程的杀器,Channel会在多个goroutine之间进行通信,而lock是用来保护读写以及关闭的并发安全

因为在Go当中会有阻塞读和阻塞写的概念,Channel当中会有读写等待队列(recvqsendq)来帮助goroutine在未完成读写操作后,可以被阻塞挂起,然后等待Channel通信到来的时候,再被唤醒

recvq and sendq

recvqsendq是 waitq类型

type waitq struct {
  first *sudog   //sudo 队列的队头指针
  last  *sudog   //sudo 队列的队尾指针
}

sudog可以看作是对阻塞挂起的g的一个封装。然后用多个sudog来构成等待队列

以下是sudog结构源码:

type sudog struct {
  // The following fields are protected by the hchan.lock of the
  // channel this sudog is blocking on. shrinkstack depends on
  // this for sudogs involved in channel ops.

  g *g

  next *sudog
  prev *sudog
  elem unsafe.Pointer // data element (may point to stack)


  // isSelect indicates g is participating in a select, so
  // g.selectDone must be CAS'd to win the wake-up race.
  isSelect bool

  // success indicates whether communication over channel c
  // succeeded. It is true if the goroutine was awoken because a
  // value was delivered over channel c, and false if awoken
  // because c was closed.
  success bool
  
  c        *hchan // channel
}
  • g:绑定的goroutime
  • next:后指针
  • prev:前指针
  • elem作为收发数据的容器
    • 当向channel发送数据时,elem代表将要写进channel的元素地址
    • 当从channel读取数据时,elem代表要从channel中读取的元素地址
  • isSelect:标识是不是因为select操作封装的sudog
  • success
    • true:表示这个sudog是因为channel通信唤醒的
    • 否则为false,表示这个sudog是因为channel close唤醒的