channel结构体的源码位于/runtime/chan.go中
channel是Go语言内置的核心类型,可以将其看做一个管道,channel和goroutine一起为go并发编程提供了最优雅和便利的方案
在Go中有一句经典名言,永远不要通过共享内存来通信,而是要通过通信来共享内存,channel便是用于实现goroutine间通信的
channel提供了三种类型
1.单向只能发送:chan<- struct{} 只能发送
2.单向只能接收:<-chan struct{} 只能从chan里接收
3.双向即可发送也可接收:chan struct{} 既能接收也能发送
nil是channel的零值,对值是nil的channel发送和接收总是会阻塞
- buf是带缓冲的channle所特有的结构,是个循环链表,用来存储缓存数据
- sendx和recvx是用于记录buf中发送和接收的index
- lock是个互斥锁,目的是为了保证goroutine以先进先出FIFO的方式进入结构体
- recvq和sendq分别是往channel接收或发送数据的goroutine所抽象出来的数据结构,是个双向链表
- 创建channel实际上就是在内存中实例化了一个hchan结构体,并返回一个chan指针
- channel在函数间传递都是使用的这个指针,这就是为什么函数传递中无需使用channel的指针,而是直接用channel就行了,因为channel本身就是一个指针
channel发送和接收数据:
1.有缓冲channel
发送数据前,会先锁住hchan这个结构体,然后逐步往buf中填充数据(从goroutine中copy数据到buf),然后解锁
接收数据前,同样会先锁住hchan这个结构体,然后逐步从buf中获取数据(buf中copy数据到goroutine),然后解锁
2.有缓冲channel数据满了怎么办
当有缓冲channel数据满了,当goroutine继续发送数据会主动调用Go的调度器,让当前goroutine等待,并且让出内核线程M,交给其他goroutine使用,同时channel也会被抽象成含有指针和send元素的sudog结构体,保存到*sendq中等待被唤醒,那当前goroutine什么时候被唤醒呢?在有其他goroutine接收数据后被唤醒
3.有缓冲channel为空的情况
当channel中无数据时,goroutine接收数据操作会主动调用Go的调度器,让当前协程等待,并且让出内核线程M,交给其他goroutine使用,同时channel也会被抽象成含有指针和send元素的sudog结构体,保存到*recvq中等待有其他goroutine写入数据时被唤醒
此时,如果channel中有数据写入数据,会发现并没有锁住channel,然后将数据放入buf中,而是直接将数据从发送goroutine copy到了当前goroutine,这种方式非常好,在唤醒当前goroutine的过程中,无需再获得channel的锁,然后从buf中取数据,减少了内存cpoy,提高了效率
4.无缓冲channel
发送数据时,先查看到*recvq中是否有等待的goroutine,如果有直接拷贝数据,唤醒接收的goroutine,没有则保存到*sendq中等待被唤醒
接收数据时,同样先查看到*sendq中是否有等待的goroutine,如果有直接拷贝数据,唤醒发送的goroutine,没有则保存到*sendq中等待被唤醒