返回归档
🪝Golang

Golang channel

数据结构

channel的地层数据结构为**hchan**

image

type hchan struct {
	qcount   uint           //当前channel中存在多少元素数量
	dataqsiz uint           //当前channel的容量
	buf      unsafe.Pointer //channel中用户存放元素的环形缓冲区
	elemsize uint16         //channel 单个元素的大小
	closed   uint32         //channel是否被关闭 1表示已关闭
	elemtype *_type         //channel元素类型
	sendx    uint           // 发送元素进入环形缓冲区的index
	recvx    uint           // 接受元素进入环形缓冲区的index
	recvq    waitq          // channel中,因为接受而陷入阻塞的协程队列
	sendq    waitq          // channel中,因为发送而陷入阻塞的协程队列

	lock mutex
}

waitq为阻塞的协程队列

type waitq struct {
    first *sudog           //阻塞队列头部
    last  *sudog           //阻塞队列尾部
}

sudog用于包装协程的节点

type sudog struct {
    g *g

    next *sudog
    prev *sudog
    elem unsafe.Pointer 
    // ...
    c        *hchan 
}

其中的 mutex 是runtime包中,给系统运行时使用的互斥锁,使用更加地层,其直接基于semaphore原语

  • 0表示未锁定
  • 1表示锁定
  • 其他值表示有等待者

每次调用semaphore不一定都陷入内核态,go的运行时做了很多优化

  1. 无竞争时,完全在用户态解决
  2. 有竞争时,尝试用内核态自自旋
  3. 如果都无法解决时,才会使用真正的信号量陷入内核态

image

  1. 看channel是否为nil,若为nil,直接deadlock
  2. 若channel不为nil,则用mutex 锁住
  3. 检查channel是否有close
  4. 若有close
    1. 若buf中是否还有元素,若有直接返回数据,解锁mutex
    2. 若buf中无元素,则返回channel 数据的zero value
  5. 若无close
    1. 写入阻塞队列中无元素
      1. 若buf中有元素,读取数据,返回
      2. 若buf中无元素且写入阻塞队列中无元素,将此读取协程加入此channel的readq中,go_park,协程进入阻塞状态,解锁mutex
    2. 写入阻塞协程队列中有元素,从阻塞写入协程链表中第一个协程唤醒,读取数据,解锁mutex,返回数据

image

  1. 若channel为nil,写入不成功,panic
  2. 若channel已经closed,panic
  3. mutex
  4. 若channel的buf还有空间,直接写入数据,返回
  5. 若读取阻塞协程队列不为空,从读取阻塞队列中读取数据,返回
  6. 若buf中无空间且读取阻塞队列中无元素,将g加入写入阻塞队列,调用go_park,进入阻塞状态

关闭

image

  1. 若channel为nil,关闭失败,发生panic
  2. channel不为nil,使用mutex锁住
  3. 若channel已经close,此时再close,就发生了重复close事件,panic
  4. 将所有的读取阻塞协程唤醒
  5. 若写入阻塞协程还存在,则发生panic
  6. mutex解锁`