cloudwu/coroutine 源码分析
1 与其它协程库使用对比
这个 C 协程库是云风(cloudwu) 写的,其接口风格与 Lua 协程类似,并且都是非对称 stackful 协程。这个是源代码中的示例:
网站建设哪家好,找创新互联!专注于网页设计、网站建设、微信开发、微信小程序开发、集团企业网站建设等服务项目。为回馈新老客户创新互联还提供了旬阳免费建站欢迎大家使用!
#include "coroutine.h"
#include
struct args
{
int n;
};
static void
foo(struct schedule *S, void *ud)
{
struct args *arg = ud;
int start = arg->n;
int i;
for (i = 0; i < 5; i++)
{
printf("coroutine %d : %d\n", coroutine_running(S), start + i);
coroutine_yield(S);
}
}
static void
test(struct schedule *S)
{
struct args arg1 = {0};
struct args arg2 = {100};
int co1 = coroutine_new(S, foo, &arg1);
int co2 = coroutine_new(S, foo, &arg2);
printf("main start\n");
while (coroutine_status(S, co1) && coroutine_status(S, co2))
{
coroutine_resume(S, co1);
coroutine_resume(S, co2);
}
printf("main end\n");
}
int main()
{
struct schedule *S = coroutine_open();
test(S);
coroutine_close(S);
return 0;
}
这段代码输出:
main start
coroutine 0 : 0
coroutine 1 : 100
coroutine 0 : 1
coroutine 1 : 101
coroutine 0 : 2
coroutine 1 : 102
coroutine 0 : 3
coroutine 1 : 103
coroutine 0 : 4
coroutine 1 : 104
main end
与其等价的 Lua 代码为:
local function foo(args)
local start = args.n
for i = 0, 4 do
print(string.format('coroutine %s : %d', coroutine.running(), start + i))
coroutine.yield()
end
end
local function test()
local arg1 = {n = 0}
local arg2 = {n = 100}
local co1 = coroutine.create(foo)
local co2 = coroutine.create(foo)
print('main start')
coroutine.resume(co1, arg1)
coroutine.resume(co2, arg2)
while coroutine.status(co1) ~= 'dead' and coroutine.status(co2) ~= 'dead' do
coroutine.resume(co1)
coroutine.resume(co2)
end
print('main end')
end
test()
这段代码输出:
main start
coroutine thread: 000001D62BD6B8A8 : 0
coroutine thread: 000001D62BD6BA68 : 100
coroutine thread: 000001D62BD6B8A8 : 1
coroutine thread: 000001D62BD6BA68 : 101
coroutine thread: 000001D62BD6B8A8 : 2
coroutine thread: 000001D62BD6BA68 : 102
coroutine thread: 000001D62BD6B8A8 : 3
coroutine thread: 000001D62BD6BA68 : 103
coroutine thread: 000001D62BD6B8A8 : 4
coroutine thread: 000001D62BD6BA68 : 104
main end
与其等价的 C++ 20 代码为:
#include
#include
#include
struct coroutine_running
{
bool await_ready() { return false; }
bool await_suspend(std::coroutine_handle<> h) {
_addr = h.address();
return false;
}
void* await_resume() {
return _addr;
}
void* _addr;
};
struct return_object : std::coroutine_handle<>
{
struct promise_type
{
return_object get_return_object() {
return std::coroutine_handle::from_promise(*this);
}
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
return_object(std::coroutine_handle h) : std::coroutine_handle<>(h){}
};
struct args
{
int n;
};
return_object foo(args* arg)
{
int start = arg->n;
for (int i = 0; i < 5; i++)
{
printf("coroutine %p : %d\n", co_await coroutine_running{}, start + i);
co_await std::suspend_always{};
}
}
int main()
{
args arg1 = { 0 };
args arg2 = { 100 };
auto co1 = foo(&arg1);
auto co2 = foo(&arg2);
printf("main start\n");
while (!co1.done() && !co2.done())
{
co1.resume();
co2.resume();
}
co1.destroy();
co2.destroy();
printf("main end\n");
}
这段代码输出
main start
coroutine 0x : 0
coroutine 0x : 100
coroutine 0x : 1
coroutine 0x : 101
coroutine 0x : 2
coroutine 0x : 102
coroutine 0x : 3
coroutine 0x : 103
coroutine 0x : 4
coroutine 0x : 104
main end
对比三段代码,可以看到 C 版本比 Lua 版本多了 coroutine_open 和 coroutine_close,C 版本需要保存一个全局状态 S。Lua 版本无法在创建协程的时候指定参数,必须在之后的 resume 把参数传递给 foo。C++ 20 版本需要手写第 5~35 行的框架代码才能达到同样的效果。抛开这三段代码的差异,能看到协程库的共性:创建协程(create)、恢复协程(resume)、让出协程(yield)、销毁协程(destroy)。cloudwu/coroutine 的源代码行数非常少,所以以此分析一个协程库如何实现这些机制。
在分析代码之前需要明确协程的一些基本概念,coroutine 的 co 并不是 concurrency,而是 cooperative。协程就是能协作执行的例程(routine)。函数(function)也是例程,但不是协程。协程和函数是类似概念。协程和线程不类似,和线程类似的概念是纤程(Fiber)。关于协程和纤程的区别可以看 N4024。协程按能不能在嵌套栈帧中挂起(suspend),分为:stackless 协程 和 stackful 协程。stackless 协程只能在最顶层栈帧挂起,stackful 协程能在任意栈帧挂起。C++ 20 协程是 stackless 协程。所以在上面的代码中,如果 foo 再调用一个函数 bar,bar 就无法使用 co_await。但是 C 版本和 Lua 版本的协程可以这样,所以它们是 stackful。如果 a resume b,则称 a 为 b 的 resumer。协程还可以按控制流(control flow)切换方式分为:对称(symmetric)协程和非对称(asymmetric)协程。非对称协程通过 yield 切换到其 resumer,不需要指定切换到哪个协程。对称协程每次切换都需要指定切换到哪个协程,它可以切换到任意协程。C 版本和 Lua 版本的协程都只支持非对称协程,但是可以通过非对称协程实现对称协程。C++ 20 协程两者都支持。
源代码只有两个文件 coroutine.h 和 coroutine.c。
2 coroutine.h
先看 coroutine.h,有 4 个宏定义
Name | Value |
---|---|
COROUTINE_DEAD | 0 |
COROUTINE_READY | 1 |
COROUTINE_RUNNING | 2 |
COROUTINE_SUSPEND | 3 |
显然,这个 4 个宏定义了协程的四种状态,协程创建完还没执行是 COROUTINE_READY,正在执行是COROUTINE_RUNNING,被挂起是 COROUTINE_SUSPEND,已经结束是 COROUTINE_DEAD。为方面阅读,下面直接用 Ready、Running、Suspend、Dead 指代。
一个 schedule 结构体,保存了用来做协程调度的信息,是一个协程组的全局状态。注意协程并没有调度器,也不需要像进程和线程那样的调度算法。每次协程的切换,切换到哪个协程都是固定的。所有属于同一个 schedule 的协程可以视为一个协程组。schedule 拥有这个协程组的信息。
一个函数指针定义 coroutine_func,这个函数表示协程的执行内容,也就是协程执行的时候会调用这个函数。该函数有一个参数 ud,ud 是 user data 的缩写,user 指代调用接口的程序员,user data 被用来传递数据。Lua 的 userdata 也用于 C 和 Lua 之间数据传递。
两个针对全局状态 S 的操作:coroutine_open 和 coroutine_close
五个针对单个协程的操作:coroutine_new、coroutine_resume、coroutine_status、coroutine_running、coroutine_yield。其中 coroutine_new 也有一个 ud 和 coroutine_func 的参数对应。根据这些状态和操作可以画出协程的状态图。
stateDiagram [*] --> Ready : coroutine_new Ready --> Running : coroutine_resume Running --> Suspend : coroutine_yield Suspend --> Running : coroutine_resume Running --> Dead : normal finish / coroutine_close Dead --> [*]标题名称:cloudwu/coroutine 源码分析
文章URL:http://myzitong.com/article/dsoijpd.html