大家或多或少的接触过了channel 了,我今天想学一下channel的数据结构。
我有的时候喜欢从一个对象提供的公共接口/功能来猜测这个对象的数据结构,那么今天我们来猜一猜channel。
1,channel可以存储数据,而且是先进先出,所以我猜测其中包含一个数组或者链表之类的用来保存数据。
2,channel可以多线程的读写,所以应该还有一个锁,来支持并发。
3,然后应该还会有一些 长度的属性。
4,close 一个closed的channel 会panic,所以有一个状态字段保存是否closed,比较科学。
5,保存数据的数组或者的链表应该是定长的,最起码应该有一个最大长度,不然的话,就可以一直无限写入channel,
大致的猜测结果有了,那我们来看一下channel是怎么定义的?
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 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 }
可以看到,猜到的部分,基本正确。那我们现在来仔细说说这个结构。
首先,我们定义一个channel
ch := make(chan int, 2)
当使用
<-ch
ch<- 的时候,就会使用mutex锁住这个ch。然后把数据从goruntine copy到 ch/把数据从ch copy到goruntine,最后释放锁。
这样就可以实现通过交流来共享内存。Do not communicate by sharing memory;instead,share memory by communicating。
当ch满了之后,继续使用ch<-,当前goruntine 会阻塞,
当ch空了之后,继续使用<-ch,当前goruntine 也会阻塞。
但是,当前goruntine被阻塞之后,什么时候会被唤醒呢?
但send 阻塞时,goruntine和send的值会被封装成sudog,放到sendq中等待,
当rev之后,这时候ch不在是满的,sendq 等待列表中的一个数据sudog取出来放到ch中,然后阻塞的goruntine被唤醒
type waitq struct {
first *sudog
last *sudog
}
当ch为中的时候,继续send,也会阻塞,但是接着rev操作和上面略有不同
rev并不锁住ch,而是直接将数据copy给sendq列表中的一个,这样减少了部分锁,增加了效率,确实巧妙