Go SDK/src/runtime/chan.go

  1. type hchan struct {
  2. qcount uint // 环形队列中剩余元素个数
  3. dataqsiz uint // 环形队列长度,make函数指定的缓冲区容量
  4. buf unsafe.Pointer // 指向环形队列的指针
  5. elemsize uint16 // 队列内每个元素的大小
  6. closed uint32 // 通道的关闭标识位
  7. elemtype *_type // 队列内元素类型
  8. sendx uint // 环形队列下标,指示元素写入时存放到队列中的位置
  9. recvx uint // 环形队列下标,指示下一个读取的元素的位置
  10. recvq waitq // 等待读取元素的goroutine队列(双向链表)
  11. sendq waitq // 等待写入元素的goroutine队列(双向链表)
  12. lock mutex // 互斥锁,channel不允许并发读写
  13. }

特性

  1. 无缓冲区通道
    1. 在没有等待的读操作时,写操作会阻塞当前协程;
    2. 在没有等待的写操作时,读操作会阻塞当前协程;
    3. 从已经close的无缓冲通道读数据时,返回通道类型的零值;
  2. 有缓冲区通道
    1. 通道已满时,写操作会阻塞当前协程;
    2. 通道为空时,读操作会阻塞当前协程;
    3. 通道未满且不为空时,读和写都不会阻塞当前协程;
    4. 从已经close的有缓冲通道读取数据时,若通道内存在未读取数据则返回该数据,否则返回通道类型的零值;
  3. channel为nil时,读写操作都会阻塞当前协程;
  4. 向已经关闭的channel写数据时,会引发panic异常;
  5. 重复关闭channel或关闭为nil的channel时,会引发panic异常;

    存储结构

    image.png

    向channel写数据

  6. 如果等待读取队列recvq不为空,说明channel没有缓冲区或缓冲区中没有数据,此时直接从recvq中取出一个G,数据写入并唤醒这个G后,结束当前操作;

  7. 如果缓冲区中存在空余位置,将数据写入缓冲区后结束当前操作;
  8. 如果缓冲区中没有空余位置且recvq为空,创建sudog并将当前G和数据放入sudog,将sudog放入sendq后当前G进入睡眠,等待被读操作唤醒;

image.png

向channel读数据

  1. 如果channel无缓冲区且等待写入队列sendq不为空,直接从sendq中取出sudog,从sudog中读出数据后将sudog中的G唤醒,结束当前操作;
  2. 缓冲区中有数据且写入队列sendq不为空时,先从buf[recvx]处读取数据,在从sendq中取出sudog,将sudog中的数据写入buf[sendx]后唤醒sudog中的G,结束当前操作;
  3. 缓冲区中有数据且写入队列sendq为空时,从buf[recvx]处读取数据,结束当前操作;
  4. 缓冲区中无数据时,创建sudog并将当前G放入sudog中,将sudog放入sendq后当前G进入睡眠,等待被写操作唤醒;

image.png

关闭channel

  1. 关闭channel时,会把等待读取队列recvq中的G全部唤醒,并写入通道类型的零值;
  2. 关闭channel时,会把等待写入队列sendq中的G全部唤醒,但这些G会引发panic异常