go channel 原理

大家或多或少的接触过了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列表中的一个,这样减少了部分锁,增加了效率,确实巧妙

posted @ 2019-07-02 14:19  JustDotNet  阅读(585)  评论(0编辑  收藏  举报