channel底层实现

31 阅读2分钟

参考:cloud.tencent.com/developer/a…

channel设计原理:以通信的手段共享内存

channel底层结构:

type hchan struct {
  qcount   uint           // 队列中的数据个数
  dataqsiz uint           // 环形队列的大小,channel本身是一个环形队列
  buf      unsafe.Pointer // 存放实际数据的指针,用unsafe.Pointer存放地址,为了避免gc
  elemsize uint16 
  closed   uint32 // 标识channel是否关闭
  elemtype *_type // 数据 元素类型
  sendx    uint   // send的 index
  recvx    uint   // recv 的 index
  recvq    waitq  // 阻塞在 recv 的队列
  sendq    waitq  // 阻塞在 send 的队列
  
  lock mutex  // 锁 
}

channel本身有buf、sendx、recx、sendq、recq、lock几部分组成

  • buf 是有缓冲的channel特有的数据结构,是一个循环链表,用来储存缓存数据
  • sendx、recx用来记录循环链表中发送方和接收方的index
  • sendq、recq用来记录接收和发送的goroutine抽象出来的数据
  • lock 互斥锁

具体的过程是这样的:

  1. 当有缓存未满的时候,发送和接收其实都操作buf这个循环链表。

    • 加锁
    • 取/发送数据
    • 解锁

    发送数据是把数据放入buf中,接收的过程其实是把数据copy到接收goroutine中,管道的由来

  2. 如果缓存满了,再进行发送

    • 协程是用户态的线程,需要调度器自己去调度。现在会停掉发送方的goroutine并将其放入一个等待队列中,然后m继续执行本地队列中下一个G
    • 同时G1会抽象成有G指针和发送元素的sudog结构体,被存入sendq中
  3. 如果缓存空,进行接收

    • 其实过程和缓存满类型,都是G和M解绑,放入等待队列。recq会储存一个G指针和接收元素抽象成sudog结构体
  4. 如果缓存满之后,再发送,后面又有接收者

    • 首先是从buf中取数据copy到接收方
    • 对于原本阻塞休眠的G,channel会将其从等待队列中推出,将G当时send的数据放入buf中。而G被唤醒之后,会被放入可运行的goroutine队列中,被调度执行
  5. 如果缓存空,有接收方接收,然后接收方被放入了recq缓存中,再有发送方发送一个数据

    • 这个过程和发送阻塞然后接收类似,都会唤醒原来被阻塞的channel
    • 但是发送方的数据不会放到buf中,而是在recq的G唤醒之后,直接copy的recq的G中。这样就省去了一个加锁的操作