chan: 通过通信来共享

libmill是一个基于c语言开发的go风格协程库,实现了go风格的协程操作、chan操作、非阻塞的网络io操作等等,是一个不错的linux平台下c风格协程库实现。如果你想从0到1的快速了解如何开发一个协程库,libmill将是一个非常不错的案例;如果你想更深入地了解go,libmill里面也借鉴了go的一些设计思想;或者你想在生产环境中使用,开发者也提供了一个更健壮的版本libdill。

设计思想

通信串行处理(CSP)和核心思想是,“通过通信来共享数据,而非通过共享数据来通信”,这是也是指导go语言chan的设计思想,libmill中也基于这一思想实现了chan。chan可以理解为管道,当有多个并发任务实体要共享数据时,通过这个chan将数据传递给对方,而非通过共享存储器数据的方式。

采用共享存储器数据的方式,也是一种思路,但是就要涉及到一些同步互斥的处理,这给并发编程带来了不小的负担,经常会有功能正确性问题、性能差问题出现,开发效率也低。

现在通过CSP的方式,我们将要共享的数据以值的形式通过chan发送给对方,接收到这个值的协程变为这个值的owner,再继续进行后续处理。思路清晰、编码简单、性能也好。

ps:至于为什么性能好,需要了解下进程、线程实现同步互斥所依赖的futex具体工作原理,也需要了解下chan的实现原理,以go为例,chan内部有锁保护队列,但是这里的锁也是基于sync.Mutex实现的,不是基于futex的。

源码实现

file: chan.h

// choose语句是根据chan的状态来决定是否执行对应动作的分支控制语句
//
// 每个协程都会有一个choose数据结构来跟踪其当前正在执行的choose操作
struct mill_choosedata {
    // 每个choose语句中,又包含了多个从句构成的列表
    struct mill_slist clauses;
    // choose语句中otherwise从句是可选的,是否有otherwise从句,0否1是
    int othws;
    // 当前choose语句中,是否有指定deadline,未指定时为-1
    int64_t ddline;
    // 当前choose语句中,chan上事件就绪的从句数量
    int available;
};

// chan ep是对chan的使用者的描述,每个ep要么利用chan发送消息,要么接收消息
//
// 每个chan有一个sender和receiver,所以每个chan包括了sender、receiver两个mill_ep成员
struct mill_ep {
    // 类型(数据发送方 或 数据接收方)
    enum {MILL_SENDER, MILL_RECEIVER} type;
    // 初始化的choose操作的序号
    int seqnum;
    // choose语句中引用该mill_ep的从句数量
    int refs;
    // choose语句中引用该mill_ep并且已经处理过的数量
    int tmp;
    // choose语句中仍然在等待该mill_ep上事件就绪的从句列表
    struct mill_list clauses;
};

// chan
struct mill_chan_ {
    // channel里面存储的元素的尺寸(单位字节)
    size_t sz;
    // 每个chan上有一个seader和receiver
    // sender记录了等待在chan上执行数据发送操作的从句列表,receiver则记录了等待接收数据的从句列表
    struct mill_ep sender;
    struct mill_ep receiver;
    // 当前chan的引用计数(引用计数为0的时候chclose才会真正释放资源)
    int refcount;
    // 该chan上是否已经调用了chdone(),0否1是
    int done;
    // 存储消息数据的缓冲区紧跟在chan结构体后面
    // - bufsz代表消息缓冲区可容纳的最大消息数量
    // - items表示缓冲区中当前的消息数量
    // - first代表缓冲区中可接收的下一个消息的位置,缓冲区末尾有一个元素来存储chdone()写的数据
    size_t bufsz;
    size_t items;
    size_t first;
    // 调试信息
    struct mill_debug_chan debug;
};

// 该结构体代表choose语句中的一个从句,例如in、out、otherwise
struct mill_clause {
    // 等待this.ep事件就绪的从句列表(迭代器)
    struct mill_list_item epitem;
    // 该从句隶属的choose语句所包含的从句列表(迭代器)
    struct mill_slist_item chitem;
    // 创建该从句的协程
    struct mill_cr *cr;
    // 该从句正在等待的chan endpoint
    struct mill_ep *ep;
    // 对于out从句,val指向要发送的数据;对于in从句,val为NULL
    void *val;
    // 该从句执行完成后要跳转到第idx个从句
    int idx;
    // 是否有与当前从句匹配的pee(比如当前从句为ch上的写,是否有ch上的读从句),0否1是
    int available;
    // 该从句是否在chan的sender或receiver列表中,0否1是
    int used;
};

// 返回包含该endpoint的chan
struct mill_chan_ *mill_getchan(struct mill_ep *ep);

file: chan.c

Last updated

Was this helpful?