golang中的channel到底是什么-2

128 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情

3、channel的源码分析

channel 源码重点关注hchan 数据结构以及 makechan 、chansend、chanrev、closechan 几个方法

3.1、hchan

2.png 从对chan的用法中可以了解到,chan 区分是否有缓冲,runtime.hchan中的dataqsiz 表示缓冲区的数量,为0 即为无缓冲; runtime.hchan 结构体中的五个字段 qcount、dataqsiz、buf、sendx、recvx 构建底层的循环队列:

  • qcount — Channel 中的元素个数;
  • dataqsiz — Channel 中的循环队列的长度;
  • buf — Channel 的缓冲区数据指针;
  • sendx — Channel 的发送操作处理到的位置;
  • recvx — Channel 的接收操作处理到的位置; 除此之外,elemsize 和 elemtype 分别表示当前 Channel 能够收发的元素类型和大小;sendq 和 recvq 存储了当前 Channel 由于缓冲区空间不足而阻塞的 Goroutine 列表,这些等待队列使用双向链表 runtime.waitq 表示,链表中所有的元素都是 runtime.sudog 结构; 接下来重点学习几个关键的方法:

3.2、channel 创建

编译器会将 ch := make(chan bool,10) 结构代码转化为 makechan,上述过程不重要,重点观察makechan的实现

func makechan(t *chantype, size int) *hchan {
   elem := t.elem
   // compiler checks this but be safe.
   if elem.size >= 1<<16 {
      throw("makechan: invalid channel element type")
   }
    // ...
   mem, overflow := math.MulUintptr(elem.size, uintptr(size))
   // ...
   // Hchan does not contain pointers interesting for GC when elements stored in buf do not contain pointers.
   // buf points into the same allocation, elemtype is persistent.
   // SudoG's are referenced from their owning thread so they can't be collected.
   // TODO(dvyukov,rlh): Rethink when collector can move allocated objects.
   var c *hchan
   switch {
   case mem == 0:
      // Queue or element size is zero.
      c = (*hchan)(mallocgc(hchanSize, nil, true))
      // Race detector uses this location for synchronization.
      c.buf = c.raceaddr()
   case elem.ptrdata == 0:
      // Elements do not contain pointers.
      // Allocate hchan and buf in one call.
      c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
      c.buf = add(unsafe.Pointer(c), hchanSize)
   default:
      // Elements contain pointers.
      c = new(hchan)
      c.buf = mallocgc(mem, elem, true)
   }
   c.elemsize = uint16(elem.size)
   c.elemtype = elem
   c.dataqsiz = uint(size)
    // ...
   return c
}

makechan 分为几个部分

  • 元素大小检测,必须小于1 << 16 也就是65536 字节
  • 计算buf中内存占用 buf 数量 ✖️ buf元素大小 得出 mem
  • 对于buf 中元素分类进行内存分配
    • mem = 0 即 无缓冲队列或者 缓冲元素size 等于0 直接分配 hchanSize大小
    • buf 中元素不含指针,将buf存储到hchan结构尾部,合并分配一次内存
    • buf 中元素含有指针,分别对 hchan 以及buf 分配两次内存