Skip to content

Caturra000/co

Repository files navigation

co

TL;DR

简单的协程库,有以下特性:

  • 非对称式+有栈协程
  • 适用于Linux x86_64环境
  • 提供友好的函数调用方式、闭包和协程检测机制
  • header-only支持
  • 协程化的POSIX接口适配

接口

接口很简单

open()

每个线程下的coroutine环境都需要co::open()获取

co::open()操作可以重复调用,仅会在首次调用时初始化环境

createCoroutine()

上述co::open()将返回一个co::Environment&实例

你可以通过它来创建协程,创建方式和std::thread的构造函数差不多,接受任意可调用对象

比如有一个函数接口为print(int, char),可以这么写

auto coroutine = environment.createCoroutine(print, 1, 'a')

返回类型为std::shared_ptr<co::Coroutine>

注意创建好的协程co::Coroutine并不会立刻启动

resume()

不管你是启动一个协程,还是恢复协程,都要coroutine.resume()

yield()

让出自身的协程上下文

只允许当前在运行的协程让出,既co::this_coroutine::yield()

test()

co::test()返回一个bool,表示当前执行的控制流是否位于协程上下文

这个有什么用?看你发挥

比如有栈协程本身栈空间就是比较小的(相比完整的线程),但是堆分配本身又很慢

那么在一些空间吃紧的场合下可以用test()来区分,实现协程上下文使用堆分配,线程/进程上下文使用栈分配

这需要应用层自己去实现

简单示例

#include <iostream>
#include <memory>
#include <vector>
#include "co.hpp"

void where() {
    std::cout << "running code in a "
              << (co::test() ? "coroutine" : "thread")
              << std::endl;
}

void print1() {
    std::cout << 1 << std::endl;
    co::this_coroutine::yield();
    std::cout << 2 << std::endl;
}

void print2(int i, co::Coroutine *co1) {
    std::cout << i << std::endl;
    co1->resume();
    where();
    std::cout << "bye" << std::endl;
}

int main() {
    auto &env = co::open();
    auto co1 = env.createCoroutine(print1);
    auto co2 = env.createCoroutine(print2, 3, co1.get());
    co1->resume();
    co2->resume();
    where();
    return 0;
}
// 1
// 3
// 2
// running code in a coroutine
// bye
// running code in a thread

进阶:POSIX接口

co支持POSIX风格的接口:比如用co::read替代系统调用read

其目的是以直观、同步的写法来完成异步事件,很轻松地完成高并发的服务器和客户端

目前已支持:

  • co::read
  • co::write
  • co::accept4
  • co::connect
  • co::sleep
  • co::usleep
  • co::poll

示例可以看test_posix前缀的文件(服务端客户端),仅要求fdNONBLOCK形式

原理还是控制流的切换,并且搭配epoll来作为一个隐藏的调度器

超时处理

使用co::poll可以定制每一个读写操作的超时时间,方便进行异常处理

并且允许co::poll(nullptr, 0, timeout)直接作为定时器使用

但是对于简单的定时任务更加建议用co::usleep()co::sleep()

事件机制

目前针对协程没有足够方便的事件通知机制,以降低生产者-消费者模型的编写难度,但是可以简单处理

如果只是一对一,可以用pipefd结合co::read即可,主动通知事件直接写1字节进去

如果是一对多或者多对多,可以自己写一个简单的wait / notify(见示例)

或者干脆用co::usleep等接口,消费者定时轮询,不需要生产者做任何事情

Benchmark

作为比较的库有:

  • 作为未来标准的boost::asio
  • 奇虎360基于libeventmuduo定制的evpp

co用到的bench文件在这里,其它库使用的bench文件在这里

表中结果的单位为MiB/s

(threads / sessions) \ server boost asio qihoo360 evpp co server
2 / 10 2981.57 3264.58 4096.6
2 / 100 2467.58 2090.98 2998.5
2 / 1000 1829.49 1585.79 1919.79
4 / 10 1079.29 631.259 4086.21
4 / 100 2948.08 2350.77 4207.58
4 / 1000 2009.47 1911.82 2200.29
8 / 10 590.297 766.125 788.441
8 / 100 2238 2432.45 3425.91
8 / 1000 1877.09 1866.43 2049.31

闲聊

关于这个协程库,它的实现方式和微信libco是一致的,感兴趣的话可以看下我写的libco关键源码剖析(Caturra/RTFSC/libco

另外我对云风大佬的coroutine也挺感兴趣,他的做法恰好是和这个库相反的对称式协程加上共享栈模式,有时间也可以试着实现一下

不过在这之前,我还是想想,该如何把我之前写的网络库进行协程化改造(第三版coming soon!)

About

协程库,实现跟名字一样很短

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages