内部结构

Go语言channel是first-class的,意味着它可以被存储到变量中,可以作为参数传递给函数,也可以作为函数的返回值返回。作为Go语言的核心特征之一,虽然channel看上去很高端,但是其实channel仅仅就是一个数据结构而已,结构体定义如下:

struct    Hchan
{
    uintgo    qcount;            // 队列q中的总数据数量
    uintgo    dataqsize;         // 环形队列q的数据大小
    uint16    elemsize;          // 当前使用量
    bool    closed;              // 关闭标志
    uint8    elemalign;
    Alg*    elemalg;        // interface for element type
    uintgo    sendx;            // 发送index
    uintgo    recvx;            // 接收index
    WaitQ    recvq;            // 因recv而阻塞的等待队列
    WaitQ    sendq;            // 因send而阻塞的等待队列
    Lock;
};

可能会有人疑惑,结构体中只看到了队列大小相关的域,并没有看到存放数据的域啊?如果是带缓冲区的chan,则缓冲区数据实际上是紧接着Hchan结构体中分配的。

c = (Hchan*)runtime.mal(n + hint*elem->size);

另一个重要部分就是recvq和sendq两个链表,一个是因读这个通道而导致阻塞的goroutine,另一个是因为写这个通道而阻塞的goroutine。如果一个goroutine阻塞于channel了,那么它就被挂在recvq或sendq中。WaitQ是链表的定义,包含一个头结点和一个尾结点:

struct    WaitQ
{
    SudoG*    first;
    SudoG*    last;
};

队列中的每个成员是一个SudoG结构体变量。

struct    SudoG
{
    G*    g;        // g and selgen constitute
    uint32    selgen;        // a weak pointer to g
    SudoG*    link;
    int64    releasetime;
    byte*    elem;        // data element
};

该结构中主要的就是一个g和一个elem。elem用于存储goroutine的数据。读通道时,数据会从Hchan的队列中拷贝到SudoG的elem域。写通道时,数据则是由SudoG的elem域拷贝到Hchan的队列中。

读写channel操作

空通道是指将一个channel赋值为nil,或者定义后不调用make进行初始化。按照Go语言的语言规范,读写空通道是永远阻塞的。其实在函数runtime.chansend和runtime.chanrecv开头就有判断这类情况,如果发现参数c是空的,则直接将当前的goroutine放到等待队列,状态设置为waiting。

对已关闭的chan读写

对未初始化的chan读写:读写未初始化的 chan 都会阻塞