返回归档
🪝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解锁`