go基础-chan原理

730人浏览 / 0人评论

chan结构(src/runtime/chan.go)

type hchan struct {
	qcount   uint           // total data in the queue
	dataqsiz uint           // size of the circular queue // 这好像是个拼写错误
	buf      unsafe.Pointer // points to an array of dataqsiz elements
	elemsize uint16
	closed   uint32
	elemtype *_type // element type
	sendx    uint   // send index
	recvx    uint   // receive index
	recvq    waitq  // list of recv waiters
	sendq    waitq  // list of send waiters
	lock     mutex
}
  1. chan的数据缓冲区是一个环形队列,队列长度即为创建时指定的长度;对于环形队列,需要记录下一条数据的写入索引(sendx),以及下一次读取数据的索引(recvx)
  2. chan无数据时,读取的goroutine会堵塞,等待写入数据激活;chan数据已满时,写入的goroutine同样会堵塞,等待读取数据激活;chan的等待列表是一个链表(recvq,sendq),由于需要从头部唤醒,并且在尾部增加新的等待goroutine,所以使用的是双向链表
type waitq struct {
	first *sudog
	last  *sudog
}

chan读写

1. 写数据(chansend)(src/runtime/chan.go)

  • 判断chan是否为nil,是则panic(unreachable)
  • 判断chan是否已经准备好接受数据,否则返回false
  • hchan.lock加锁
  • 判断chan是否已close,是则panic(send on closed channel)
  • 判断是否存在等待读取goroutine,是则直接将数据写入,并唤醒读取goroutine
  • 缓冲区可用,将数据写入缓冲区,并更新循环队列写入索引及数据大小
  • 缓冲区已满时,将当前goroutine加入sendq,等待唤醒

.png

2. 读数据(chanrecv)(src/runtime/chan.go)

  • 判断chan是否为nil,是则panic(unreachable)
  • 判断chan是否已经准备好读取数据,否则返回false
  • hchan.lock加锁
  • 判断chan是否已close,且数据缓冲区为空,是则返回false
  • 判断是否存在等待写入goroutine,存在时:
    • 无缓冲chan:直接读取数据,并唤醒写入goroutine
    • 带缓冲chan:从缓冲区头部取出一个元素,从sendq中取出一个goroutine,写入数据到缓冲区尾部,唤醒写入goroutine
  • 判断缓冲区是否为空,否则从缓冲区头部取出一个元素
  • 缓冲区为空时,将当前goroutine加入recvq,等待唤醒

.png

全部评论