channel(通道)
如果说goroutine是Go语言的并发执行体,那channel则是goroutine之间的通信机制。channel是进程内的通信机制,是类型相关的,即只能绑定一种数据类型。
----通道c1可以接收任意格式的数据,是因为绑定的是interface{},任何类型都实现了空接口,因此可以传输任意格式的数据。
----通道c2报错,是因为绑定的是int类型,你发送一个string类型肯定是不支持的。
func TestChannel2() {
c1 := make(chan interface{}, 2)
c2 := make(chan int, 2)
go func() {
c1 <- "lizhih"
c1 <- 2
c2 <- 2
//报错:'"lizhih"' (type string) cannot be
//represented by the type int
c2 <- "lizhih"
}()
发送和接受通道:刚开始接触的时候,很容易被chan<-和<-chan搞混。可以将chan看作通道,<-看作数据。chan<-:数据写入通道,就是发送通道了;<-chan:数据出通道,就是接收通道了。
无缓冲和有缓冲的通道
//有缓冲的通道,2个int数据
var c3 chan int = make(chan int, 2)
//无缓冲的通道
c4:=make(chan int)
----无缓冲通道:在接收前,没能力保存数据的通道
这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。如果两个goroutine没有同时准备好,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。也称之为“同步通道”
----有缓冲通道:在接收前,有能力保存一个或者多个值的通道,取决于make初始化的大小;
这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。也称之为“异步通道”
内部的数据结构
路径:runtime/chan.go
type hchan struct {
qcount uint // total data in the queue。channel中的元素个数
dataqsiz uint // size of the circular queue。channel中的循环队列的长度,创建之后就不会改变
buf unsafe.Pointer // points to an array of dataqsiz elements。指向长度为dataqsize的数组
elemsize uint16 //元素大小
closed uint32 //通道关闭状态,1-关闭,0-开启
elemtype *_type // element type。元素类型
sendx uint // send index。指向buf中存放发送的索引
recvx uint // receive index。指向buf中存放接收的索引
recvq waitq // list of recv waiters。等待channel的goroutine接收队列
sendq waitq // list of send waiters。等待channel的goroutine发送队列
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
sendq和recvq:存储了由于缓冲空间不足而阻塞的goroutine列表。
rumtime.sudog:表示一个在等待列表中给goroutine,是一个双向指针结构,成员elem就是存放数据的变量(elem unsafe.Pointer)。
unsafe.Pointer:指向任意类型的指针
(type Pointer *ArbitraryType—任意类型指针)
buf的值(makechan函数内):
1)没有缓冲,则只有chan的内存
2)元素类型不包含指针,则chan内存和buf内存是一片连续的内存。
c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
c.buf = add(unsafe.Pointer(c), hchanSize)
3)元素类型包含指针类型,则chan内存和buf内存不连续,分开创建
c = new(hchan)
c.buf = mallocgc(mem, elem, true)
发送数据(通道写chan <-)
核心逻辑:chansend
1)异常检查
channel被GC回收,或者当前状态是close,则send的时候会抛异常。
2)发送数据,先对通道上锁(每个通道结构体都有一个锁)
1.同步发送:如果等待接收队列中有阻塞的接收方,则跳过缓冲区(可以理解为缓冲区和待发发送的队列都是空的),直接奖数据拷贝到接收goroutine的内存中;
sendDirect:将发送的数据拷贝到接收的goroutine中(memmove);
goready:将接收的goroutine状态设置为_Grunnable,下一轮调度时会唤醒这个接收的 goroutine;
2.异步发送:是指当前无阻塞的接收方,且缓存还有剩余空间。则将数据缓存到缓冲区,同时记得更新sendx索引;
chanbuf:获取当前缓冲区发送索引的地址
typedmemmove:将发送的数据拷贝到buf缓冲区
3.阻塞发送:是指当前无阻塞的接收方,且缓存已满,这个时候将发送方放到等待发送队列中,此时发送的goroutine是阻塞的状态;
acquireSudog:创建一个sudog,存放当前goroutine状态和数据
c.sendq.enqueue:压入等待发送队列中
gopark:将goroutine状态设置为waitReasonChanSend
接收数据(<-chan)
核心逻辑:chanrecv
1)异常检查
chan是否为nil,为nil则会抛出异常,并阻塞
2)接收数据,先对通道上锁
1.同步接收(recv)等待发送队列有阻塞的goroutine
1.1 判断是否有缓冲区,没有则直接从等待发送队列中拷贝数据
1.2 有缓冲区的,但是发送队列不为空,说明缓冲区已满,chanbuf拿到缓冲区接收索引指针。这里有两次拷贝:
--从缓冲区到接收goroutine
--将等待发送的goroutine到缓冲区
1.3 goready
2.异步接收
缓冲区有数据,且等待队列没有发送方。
3.阻塞接收
缓冲区没有数据的时候,发送队列也没有待发送的goroutine。
关闭通道
1)异常检查
关闭一个已关闭的通道,或者通道是nil,会抛异常
2)释放待发送队列和待接收队列中的goroutine,并将状态从_Gwaiting改成_Grunnable。