c++协程原理刨析(asyncio 代码解析 c++20协程 网络库实现 coroutine)
编译看项目文档。
目前需要先到 fmt目录编译: cd third_party/fmt mkdir build cd build cmake .. make sudo make install # Optional if you want to install system-wide 再执行项目编译: $ cd asyncio $ mkdir build $ cd build $ cmake .. $ make -j
生成的可执行文件在:asyncio/build/test/st
CMakeFiles echo_client hello_world cmake_install.cmake echo_server Makefile
总结:
Sync<> pa(){
Lazy<> val = co_wait child();//走到这步时
这个创建流程:
1, 在父协程中,先创建 Lazy::promise_type。调用get_return_object,这个根据创建的prmise_type,利用 coro_handle::from_promise(promise),取到lazy协程句柄,并保存成coro(子协程句柄)。
2, 父协程调用initial_suspend,看具体返回的awaitable实现。如果ready是false,子协程进入suspend。(注意之后返回到父协程继续运行,即进入 pa的 co_wait运算符执行。)需要用coro.resume恢复。
3, 子协程恢复后执行它的函数体。
4, 子协程结束后,进入final_suspend,看返回的awaitable情况,是暂停还是继续结束。如果暂停,返回到父协程继续执行(如果co_wait还没执行,就进入co_wait运算符执行,否则就看具体在哪里暂停,就恢复那里继续执行)。
co_wait运算符执行:看waitable情况。暂停,或者继续。后续有可能到子协程函数体,或者直接就结束了。
因为是单线程,单流程。就看当时停在哪里,就继续恢复执行。
可停止的暂停点就那三个:initial_suspend, final_suspend, co_wait
co_wait 时,await_suspend返回的参数里面的协程句柄是父协程句柄。
//----------------------------------------------------------------------------
由于在main中不能用 co_wait调用协程。需要先做个协程函数 amain()。在里面再调用子协程, 子协程里面有 co_wait。
1,一个协程类,可以叫做 Future。里面一般有个coro成员,用来存协程句柄。
通过 coro.promise().value 取到promise里面的值。
它里面必须有个 promise_type的类。用来生成协程句柄,new 协程类。
promise_type 必须有 里面有两个协程暂停点:initial_suspend协程函数开始前, final_suspend 协程函数结束后
co_return的实现:return_value
创建协程类:get_return_object
另外有个数据成员 T value,用来存放值。
2,一个是可以叫做Waitable的类,必须实现三个函数:
struct Waitable {
bool await_ready() noexcept {
std::cout << "SuspendAlways not ready." << std::endl;
return false; // Always suspend immediately
}
void await_suspend(std::coroutine_handle<>) noexcept {
// This function can be used for additional actions when the coroutine is suspended
std::cout << "SuspendAlways suspended." << std::endl;
}
void await_resume() noexcept {
// This function can be used for additional actions when the coroutine is resumed
std::cout << "SuspendAlways resumed." << std::endl;
}
};
3,与这个waitable有关的几个地方,就是暂停点。
initial_suspend,final_suspend 协程开始或者结束后可以有暂停点。(协程函数的返回值,构造和销毁时。协程执行流程会先执行这里)。
co_wait expr;co_wait 运算符执行可以有暂停。这个waitable实现在协程类里面。比如Task类。
所有暂停点都是对Waitable,可暂停的对象调用,执行响应的 await_ready,await_suspend,await_resume。
- 协程的句柄恢复,会走到initial_suspend、final_suspend 中。(它也是awaitable暂停的)这个句柄是初始化时保存的,coro_, handle_.
- wait句柄恢复到await_resume。这个句柄从await_suspend的入参拿到。
co_wait的执行:首先会看协程(父,或者当前外围的)在promise_type里面有没有实现 wait_transform,这个传入后面expre的wait,也就是子协程的。然后再看协程的自己有没有重载运算符 co_wait,这个最终返回个waitable对象。
子协程暂停后,恢复到调用它的父协程位置,继续执行父协程接下来的代码。
协程最开始是编译器构造创建 promise_type,它的参数与协程函数的参数一致。也就是说协程类里面可以看到函数的参数。如果不一致,就走默认的。接下来会走到get_return_object(),返回协程句柄coro:Future<T>{handle_type::from_promise(*this)}; this就是promise_type. 这个get_return_object函数是在嵌套在Future里面的promise_type里面实现的。
C++协程的创建和执行流程如下:
1. 定义协程函数:首先需要定义一个协程函数,使用co_await和co_yield关键字来指示协程的挂起和恢复点。
2. 协程函数的返回类型:协程函数的返回类型可以是std::coroutine_handle或者一个自定义的协程类型。这个返回类型表示协程的执行状态。
3. 协程函数的调用:当调用协程函数时,首先会创建一个协程对象,并分配内存来保存协程的状态。
4. 初始化协程对象:协程对象的初始化是通过调用get_return_object函数来完成的。接着会插入 initial_suspend,协程的第一个挂起点, 这个函数会返回一个awaitable对象,表示协程的初始挂起点。如果awaitable对象的 await_ready方法return false。那就会暂停这个正在创建执行的协程。返回构建的返回值。这个对象 类里面要保存协程句柄,以便后续执行句柄的resume()。才能返回awaitable对象的 await_resume()方法。后续接着执行函数体(由于是initial_suspend暂停的,接着执行即进入函数体第一句执行)。
5. 函数体里会有co_wait运算符语句。也是标志这个函数是协程的标志。在co_wait运算符执行时,调用await_transform和co_wait:在协程对象初始化后,可以调用await_transform函数来获取一个awaitable对象,表示协程的下一个挂起点。然后可以使用co_wait函数来等待这个awaitable对象的完成。
6. 恢复协程的执行:当awaitable对象完成后,协程会从挂起点恢复执行,继续执行下一个语句。在函数完成 co_return后。进入最后一个挂起点:final_suspend()
7. 协程的完成和销毁:当协程执行完毕或者被手动终止时,会调用协程对象的析构函数来释放协程对象所占用的内存。
总结起来,C++协程的创建和执行流程包括定义协程函数、调用协程函数创建协程对象、初始化协程对象、调用await_transform和co_wait等待挂起点的完成,以及协程的恢复和完成。
每次协程函数创建返回值后,就暂停(即init_suspend),接着在暂停实现里面,就发送task的run,run里面实现resume。主要是main函数不能执行协程。让event loop取出run执行协程。
在co_wait时,重载了运算符,也执行子协程的 suspend,在暂停实现中:记录了父协程,供调试。然后将自己恢复执行。
恢复时执行 result 取结果。
协程结束时,也执行暂停,在暂停函数实现里面恢复父协程resume。
可以这样说:init_suspend 和 final_suspend ,他们的waitable里面 await_suspend的入参是和协程自身创建时存的句柄是一样的。所以恢复时,直接用创建时的句柄恢复即可。
而 co_wait expre时,await_suspend传入的协程句柄是调用方的句柄。如果说expre是子协程,那么传入的句柄是父协程。也就是执行co_wait的协程句柄。所以需要保存下来,恢复时再用这个句柄恢复。
利用 协程句柄的 address 方法可以验证这个想法。
void await_suspend(std::coroutine_handle<> awaiting)
{ std::cout << " await_suspend::awaiting:" << awaiting.address() << std::endl;}
因此我们可以说,一个协程的句柄,调用resume,可以恢复自身的 initial_suspend/final_suspend, 和它的co_await的子协程。
1. 从main开始一览整个执行过程
int main() {
asyncio::run(amain());
return 0;
}
Task<void> amain() {
auto server = co_await asyncio::start_server(
handle_echo, "127.0.0.1", 8888);
fmt::print("Serving on 127.0.0.1:8888\n");
co_await server.serve_forever();
}
这里 asyncio::run(amain());
执行流程是,
amain()返回一个协程句柄 Task<void> ,这个类就是Future,创建协程时会传入句柄 coro_handle handle_; 通过句柄取到 promise,里面存储的是协程的状态信息。
保存父协程,都是保存的promise。后续使用利用Handle::from_promise(promise*), 恢复handler!
awaitable对象有多个:suspend_initial, suspend_final的FinalAwaiter,Task的 co_wait运算符重载返回的waitable对象 Awaiter,继承自AwaiterBase。await_transform重载时,会根据传入的awaiter, 自己实例化一个A。
loop的 wait_event协程waitEventAwaiter;wait_for方法有自己的waitForAwaiter,重载了 co_await.
co_wait运算符重载,里面this是co_wait后面表达式,即子协程自己。handle_也是子协程自己。这个赋值给了self_coro_。然后返回给wo_wait awaitable对象。后续这个awaitable对象的 await_suspend,传入的参数,是调用它的,即父协程的handle协程句柄。简而言之,co_wait构造awaitable时,就是自身句柄。awaitable对象的 await_suspend永远是父协程的。
1,amain协程的调用。它被main所调用,不是其他协程调用。
先创建 0号协程 Task<void> amain(), 在get_returned_object创建完对象协程task<>后,进入第一个暂停点 init_suspend() 的时候, 返回的awaiter对象。发生暂停会走到awaiter_suspend()。这时等于是amain()这个协程暂停了。那就返回到调用者,执行run。
amain协程暂停后,返回的是 Task<void>的future对象,接着运行 asyncio::run(fut&&)流程。run将第一个task加入到事件队列中,安排执行 schedule_task。
在内部会从ready事件队列中取出刚才的amain()协程句柄,做resume。
amain() resume后,会走到 init_suspend的awaiter对象的 awaiter_resume()。完后就继续执行 amain()的函数体。
Task<void> amain() {
auto server = co_awaitasyncio::start_server
(
handle_echo, "127.0.0.1", 8888);
2,0号
amain()协程(父协程)调用 1号
start_server协程(子协程)
函数体第一句是:
auto server = co_await asyncio::start_server(
handle_echo, "127.0.0.1", 8888);
它的运行顺序是:1,计算 co_await后面的表达式,即 asyncio::start_server()返回值。2,执行 co_await expre; 3, 执行赋值 auto server = task<Server...>。
先1:生成协程对象
同样先创建start_server协程的内存和初始化。通过 get_returned_object,作为一个返回值future。
看start_server协程: Task<Server<CONNECT_CB>> start_server(...)
协程对象就是 Task<Server<CONNECT_CB>>
然后走到init_suspend,同样是走到awaiter_suspend。这时是start_server函数体暂停,
返回了future了。返回到了0号父协程 amain中。
第一步完毕。这里 init_suspend被暂停了。下面的 co_wait里面的重载实现了对子协程server的恢复!!!
2,执行 co_wait
从下面开始都是父协程amain的:
返回到父协程后,继续执行刚才中断的指令。刚才是构建子协程的return future。现在等于构建完。即生成了co_await后面的表达式了: co_await expr
的 expr
返回了。那就该执行 co_wait了。后面接的协程对象是awaitable的: Task<Server<CONNECT_CB>>
先看awaiter_transform(A&& awaiter)被实现没有。即去Task对象里面的task::promis_type找!!
里面有,可以打印源码位置信息。可以在这里打印frame_info。这里传入的awaitable对象,就是Task<Server<CONNECT_CB>>。这里的模板参数 A 就是刚才的initial_suspend的awaiter?
struct Task: private NonCopyable {
struct promise_type;
using coro_handle = std::coroutine_handle<promise_type>;
struct promise_type: CoroHandle, Result<R> {
template<concepts::Awaitable A>
decltype(auto) await_transform(A&& awaiter, // for save source_location info
std::source_location loc = std::source_location::current()) {
frame_info_ = loc;
dump_backtrace() ;
return std::forward<A>(awaiter);
}
........
再到 co_wait 运算符重载: 在task类里面!!!!
struct Task: private NonCopyable {
// 左值引用 return AwaiterBase::self_coro_.promise().result();
auto operator co_await() const & noexcept {
struct Awaiter: AwaiterBase {
decltype(auto) await_resume() const {
if (! AwaiterBase::self_coro_) [[unlikely]]
{ throw InvalidFuture{}; }
return AwaiterBase::self_coro_.promise().result();
}
};
// fmt::print("[{}] \n", frame_name());
return Awaiter {handle_};
}
//返回值是右值引用 return std::move(AwaiterBase::self_coro_.promise()).result();
auto operator co_await() const && noexcept {
struct Awaiter: AwaiterBase {
decltype(auto) await_resume() const {
if (! AwaiterBase::self_coro_) [[unlikely]]
{ throw InvalidFuture{}; }
return std::move(AwaiterBase::self_coro_.promise()).result();
}
};
return Awaiter {handle_};
}
这里代码实现了生成Awaiter,并且在继承的AwaiterBase里面.
看 Awaiter构建:Awaiter {handle_},这个handle_是Task创建时传入的,就是协程自身句柄。这个 co_await expr 就是 expre.operator co_await()。所以handler_是 Task<Server<...>>的,子协程的
struct AwaiterBase {
constexpr bool await_ready() {
if (self_coro_) [[likely]]
{ return self_coro_.done(); }
return true;
}
template<typename Promise>
void await_suspend(std::coroutine_handle<Promise> resumer) const noexcept {
assert(! self_coro_.promise().continuation_);
resumer.promise().set_state(Handle::SUSPEND);
self_coro_.promise().continuation_ = &resumer.promise();
self_coro_.promise().schedule();
}
coro_handle self_coro_ {};
};
从AwaiterBase里面有个成员 self_coro_,也就是 handle_付给了self_coro_(即Task<Server>1号协程)。
判断done返回false,进入awaiter_suspend(std::coroutine_handle<promise> parent_handle),传入的句柄,是父协程调用await的,也就是父协程0号amain的句柄。把0号父协程句柄保存到子协程自身的promise中,叫做continuation_。这个函数里面就可以保存父协程的promise。
即 父协程amain.awaiter_suspend( amain的协程句柄),这么调用的。
将父协程设置暂停标志。通过 self_coro.promise().schedule(),将子协程自身在下次调度时恢复(这次恢复是恢复到init_suspend那里,不是这个Awaiter现在这个暂停这里。
现在这个暂停是co_wait的暂停,而这个暂停的句柄就是传入的参数 resumer,所以最终需要调用 resumer.resume() 才能恢复。也就是后面在协程死亡之前的 final_suspend里面,通过contineation找的rusumer,取恢复!!!
)。
这里搞了半天进入了暂停流程,就是为了在自身中取保存父协程。记得future中,有个promise,它里面用来存储未来的数据。
co_wait的awaiter_suspend就是记录父进程,并将自己这个子协程恢复(也就是把子协程在init_suspend暂停后,到这里就恢复了。为什么走这里是因为要记录父协程)。
到这里awaiter_suspend结束,返回void。即子协程自己就暂停了。记得执行到这里是从 run()里面的 resume()执行过来的。接着循环了 void EventLoop::run_once()。
这时由于上面的schedule,在ready队列里面有子协程要恢复,所以run_once循环后会走到:
for (size_t ntodo = ready_.size(), i = 0; i < ntodo; ++i) {
.........
handle->run();
即
void run() final {
coro_handle::from_promise(*this).resume();
}
3. 继续恢复到刚才上面的init_suspend 的 suspend_resume里面。
等于在协程对象new完,进入 init_suspend,就将自己暂停。暂停后流程走到父协程,父协程又是重载了wait_transform和co_wait。在transform里面打印了父协程栈的调用帧信息。接着在重载co_wait里面,Awaiter对象保存了子协程自身句柄。把父协程句柄获取到的promise保存到子协程的promise里面。并且把父协程暂停,让子协程恢复。绕了一圈为了打印调用栈,和保存父协程句柄的promise。
上面执行到这里就是amain只是状态设置为暂停
了。其实单线程,流程是串行的。不是自己die之前,将儿子起起来。其实一直就是子协程自己玩儿。
其实这里暂停还有重要意思,就是为了等有结果返回时,在恢复自己被 co_wait处。一定记住这里的恢复只是init_suspend恢复。还没有到真正子协程干完任务有了结果后的co_wait恢复地方。那个在final_suspend那里。
3,start_server协程(子协程)恢复
也就是上面说的,从ready队列中取出子协程resume, 运行到init_suspend的awaiter_resume(),空函数。再次进入start_server函数体执行了。
最后执行到 new Server()
template<concepts::ConnectCb CONNECT_CB>
Task<Server<CONNECT_CB>> start_server(CONNECT_CB cb, std::string_view ip, uint16_t port) {
....
co_return Server{cb, serverfd};
}
co_return 执行。整个子协程要销毁了。final_suspend要执行。执行finalAwaiter, ready是false,进入 await_suspend().
finalAwaiter是在1号协程最后一个暂停点的返回waiter:final_suspend()
finalAwaiter的await_suspend负责恢复父协程
现在在1号协程TAsk<server>里面,所以参数h是它的协程句柄。从中取出上面co_wait时保存的父协程句柄:h.promise().continuation_,他就是当时保存的协程句柄resumer。是用来恢复 Awaiter那个暂停的。和父协程没关系!!!调用父协程amain的resume
.
这里也就是做了在儿子执行完时,先暂停,启动父协程。最终儿子关闭。在final中暂停,是为了把先前的 co_wait中的暂停恢复起来。理论上也对,子协程要执行完了,需要把自己的暂停恢复,让amain的co_wait处继续执行。
4,父协程恢复到 co_wait 的 Awaiter对象的 suspend_resume里面。这里返回了结果,通过子协程的promise().result()函数。
也就是给amain中 auto server= co_wait start_server();,这个语句中的server赋了值。
co_wait 的 await对象的 await_resume负责返回result。看下面:
auto operator co_await() const & noexcept {
struct Awaiter: AwaiterBase {
decltype(auto) await_resume() const {
if (! AwaiterBase::self_coro_) [[unlikely]]
{ throw InvalidFuture{}; }
return AwaiterBase::self_coro_.promise().result();
}
};
// fmt::print("[{}] \n", frame_name());
return Awaiter {handle_};
}
终于完了,执行到下一条打印:
fmt::print("Serving on 127.0.0.1:8888\n");
3, co_wait server.serve_forever()
5. 调用 co_wait server.serve_forever(); 中断返回于 co_wait loop.wait_event()
程序开始时,从main调用到协程amain()。协程开始后暂停,通过放到eventLoop事件队列恢复。
通过co_wait后面产生的子协程,返回的future对象刚产生,也在init_suspend中暂停了。(看原型 task<void> server_forever(),即构造future就是task<void>)那是如何恢复。是靠在co_wait重载中,await_suspend里设置自己马上恢复。即在死之前,设置自己resume。
恢复后执行 init_suspend的 await_resume,没干啥。
进入函数体:
直到又遇到co_wait loop.wait_event(ev)。创建这个协程对象,是个WaitEventAwaiter,是默认的,啥也没干。这是协程start_server中的co_wait,所以它会先看这个协程是否实现 await_transform。这个task<void> start_server()是实现了的。所以又打印了调用栈。然后因为wait_event返回的是个await即WaitEventAwaiter,不是task。所以co_wait是没有重载的。所以直接开始走await判断ready流程。从下面代码看的返回是false。进入await_suspend将自己server_forever设置 SUSPEND. 并将自己的协程句柄promise和协程id打包,放入event_。注册入slector_。
即注册到 epoll事件:
这时loop.wait_event协程就暂停了。
程序执行run的resume后续层层退出(单线程的)。再进入run_once循环,到selector时,因为也没有其他事件,所以等待超时设置成最大,掉进epoll的select_等待陷进:
6, 直到有客户端连接激活select_,push进队列,继续执行。
2 再次分析----loop 队列的具体流程细化
协程对象Future,它里面必须有 promise_type。
协程对象是future,这里叫task。从promise获取协程句柄通过全局方法std::coroutine_handle.from_promise(promiseObj)实现。
通常将这个协程句柄保存到自定义的协程对象中。也就是 Future
Future{ coro_handle coro=传入;
}
程序员可通过调用 std::coroutine_handle::resume() 唤醒协程。这样这里不是Awaiter的await_resume。是协程对象;不是awaiter handle。
promise_type
promise_type 是 promise 对象的类型。promise_type 用于定义类协程的行为,包括
- 协程创建方式、
- 协程初始化完成和
- 结束时的行为、
- 发生异常时的行为、
- 如何生成 awaiter 的行为以及
- co_return 的行为等等。
promise 对象可以用于记录/存储一个协程实例的状态。每个协程桢与每个 promise 对象以及每个协程实例是一一对应的。
2. 1 get_return_object
先运行amain(), 进入后, 先构建协程对象。即future,也是返回值。给协程分配内存,生成promise_type,再构建出协程内存的值,生成协程句柄。即对返回值的构建,生成 0号协程: Task<void> amain()。
通过下面 get_return_object返回生成future类型的task<void>. *this是promise_type。调用 Task构造函数。
这个future功能的类名是Task。它里面存储这个协程的句柄: coro_handle handle_;
coro_handle 就是 using std::coroutine_handler<promise_type>
handle_就是 amain协程的句柄
用来通过handler取到 promise_type, 执行自定义的schedule。(内置的resume 是handle_的)
这个handle的初始化就是从上面的 get_return_object 生成。里面又从from_promise构建。鸡生蛋,蛋生鸡的问题????。
handle是编译器自动生成代码,调用时带过来。我们只要保存。std::coroutine_handler<promise_type>::from_promise(*this) 即通过promise *this这个对象生成了handle_, 保存到了task里面。
task里面有 co_wait 重载。返回个await对象。
2.2 init_suspend 进入第一个暂停点。编译器自己插入
然后执行 init_suspend 。在await_suspend可以看的它的第一个入参,coroutine_handle<>。不是co_wait的,与co_wait无关。
是个amain协程的handle.
它位于task内部的类:这个就是协程自己的initial_suspend
返回的wait对象,ready是false。所以这个0号就暂停协程。 接着会走到 await_suspend, 由于返回void,这个协程就暂停了。继续走调用者后续逻辑,即将这个task<void>通过移动构造,给了 fut&& main,然后走run的函数内部。
0号的恢复
继续协程调用后面的语句执行。接着运行 asyncio::run(task<void>);
template<concepts::Future Fut>
decltype(auto) run(Fut&& main) {
auto t = schedule_task(std::forward<Fut>(main));
get_event_loop().run_until_complete();
if constexpr (std::is_lvalue_reference_v<Fut&&>) {
return t.get_result();
} else {
return std::move(t).get_result();
}
}
将刚才的task通过schedule_task
ScheduledTask<Fut> schedule_task(Fut&& fut) {
return ScheduledTask { std::forward<Fut>(fut) };
}
转移走了task<void>, 传入ScheduledTask,构建对象。
template<concepts::Future Fut>
explicit ScheduledTask(Fut&& fut): task_(std::forward<Fut>(fut)) {
if (task_.valid() && ! task_.done()) {
task_.handle_.promise().schedule();
}
}
上面的构造函数,通过task获取到协程句柄,利用它获取到promise。执行promise的schedule。
构建执行这个task_的schedule()方法(因为task继承了CoroHandle,会执行到:
void CoroHandle::schedule() {
if (state_ == Handle::UNSCHEDULED){
get_event_loop().call_soon(*this);
}
}
放入消息队列的ready队列,队列里面放的是promise_type,等待队列循环时执行。*this是promise_type。所以事件的执行run里面取出后,通过函数 coro_handle::from_promise(*this)再返回 协程句柄。再调用resume().
现在该执行:run方法就是将第一个仿函数放入任务队列,然后启动任务队列轮询执行。循环执行 run_once.
template<concepts::Future Fut>
decltype(auto) run(Fut&& main) {
auto t = schedule_task(std::forward<Fut>(main));
get_event_loop().run_until_complete();
即:(停止条件是 epoll selector停止,并且队列是空)死循环
void EventLoop::run_until_complete() {
while (! is_stop()) { run_once(); }
}
bool is_stop() {
return schedule_.empty() && ready_.empty() && selector_.is_stop();
}
void EventLoop::run_once() {
取消息执行
}
省去了循环队列里面的逻辑,有ready,cancel,shedule队列。
这时消息队列里面有刚才的task<void>, 取出执行run,即 handler_.resume(),恢复了刚才task 0号协程amain()的执行。
这个handle是promise_type:
这个run位于promise_type中,它继承了CoroHandle。
也就是恢复了这个协程:
Task<void> amain() {
task<void>暂停后,马上就恢复执行了。
恢复到init_suspend的await_resume
里面是空的。什么都不做。在走过第一个暂停点后,开始协程函数amain()里面第一句语句执行:
:auto server = co_await asyncio::start_server(
handle_echo, "127.0.0.1", 8888);
Note:上面的amain调用是没有co_wait的。即上面只是对返回的Task<void>做了init_suspend的awaiter对象的流程。
这里开始才有 co_wait这个运算符操作。!!!
这个就是amain()协程里面再启动的 Task<Server>协程,即:
template<concepts::ConnectCb CONNECT_CB>
Task<Server<CONNECT_CB>> start_server(CONNECT_CB cb, std::string_view ip, uint16_t port) {
a. 开始也是先构造返回值Future对象:task<server<Connect_cb>>,这个返回对象就是整个 start_server 协程的句柄。
通过:
Task get_return_object() noexcept {
return Task{coro_handle::from_promise(*this)}; 红色部分构造了协程句柄
}
,(同样task构造后,因为init_suspend的waiter是暂停,都是构造完,到 init_suspend的 awaiter_suspend()就暂停,等于子协程 start_server暂停了。返回了)接着类似上面,进入ready队列,run执行handler.resume。
返回就返回到 auto server,那个future,即返回值构建先暂停了。那就该执行 co_wait 运算符了。
b. 在amain协程中,执行co_wait:
auto server = co_await asyncio::start_server(
handle_echo, "127.0.0.1", 8888);
对co_wait的处理流程:1,如果有await_transform,可以进行装饰awaiter,再输出 awater;2,如果重载 co_wait运算符,对awaiter再处理。
1) 先执行 await_transform.这个实现位于promise_type中,先看看0号的协程amain有没有实现它。
template<concepts::Awaitable A>
decltype(auto) await_transform(A&& awaiter, // for save source_location info
std::source_location loc = std::source_location::current()) {
frame_info_ = loc;
return std::forward<A>(awaiter);
}
第一个参数awaiter是task<server>的,参数是子协程的。
await_transform的调用方肯定是父的,即this这里是父协程: task<void>::promise_type:
记录文件执行信息,frame_info中可以保存源码文件名,代码的行数,函数原型等。这里目前就是记录的0号协程 task<void>
2)co_await执行。start_server协程创建,传递到的co_wait里面,就是handle是start_server协程句柄。
源码:
Task是个Future,里面有promise_type的类型。在构建时编译会构建成传入自身的协程句柄:
这个handle_是自己task<server>的,即 co_wait 的调用它的对象就是这个自身的返回值的,自身的协程!
下面是co_wait运算符实现,要被赋给 AwaiterBase::self_coro的:也就是说它放的是子协程
然后执行到 return Awaiter {handle_}; 这个handler就是自己 task<server...>的handler。这个handle_赋值给self_coro_,self_coro_放的是task<server>,子协程。
这个Awaiter继承自AwaiterBase,是个可等待对象,进入 await_ready, 判断了isDone是false,false导致走到 await_suspend():
Awaiter是被amain调用 co_wait起来的。Awaiter::await_suspend中参数 std::coroutine_handle<Promise> resumer,传入的就是amain协程的句柄。所以它是父协程。
这个流程总结就是,在子协程的对象创建时,保存到handle;将它传递给Waiter对象中self_coro保存起来;这个Waiter对象因为是父协程调用co_wait启动的,所以它的await_suspend传入的参数就是父协程的。这样在Waiter中就可以取到父,子协程句柄了。
void await_suspend(std::coroutine_handle<Promise> resumer) const noexcept {
assert(! self_coro_.promise().continuation_);
resumer.promise().set_state(Handle::SUSPEND);把父协程的状态更新成 暂停,,这个是针对eventLooper的,是个标志。拿到它不执行。
self_coro_.promise().continuation_ = &resumer.promise(); 保存父协程的句柄
self_coro_.promise().schedule(); 将自己server task扔到ready队列。等轮到时执行resume
}
这个resumer协程句柄来自父类,即上面开始那个Task<void>协程,是现在Task<server...>的父协程。
将父协程句柄保存到子协程里面continuation_ ;并且将子协程恢复执行。这样使得子协程执行完后,可以在子协程中通过continuation_ 找到父协程,让它继续执行。
上面看的是 事件ready队列中取到,执行run,即 resume(),看的是 Task<void>的resume,即父协程调用的。await_suspend的resumer传入查看变量是Task<void>.
永远是父协程调用suspend,resume。
执行完await_suspend,其实将自己又放入到了ready队列,当event loop在找到ready中的task<server...>。
这时候,上个 task<void>的0号进程的run_once的run里面的resume()函数调用才结束。run结束了。
进入循环取任务,接着再次循环取task。
再次取出执行resume后,task<server>那个返回值 init_suspend的awaiter_resume开始执行。
注意这里
self_coro_.promise().schedule();
是Task<Server<CONNECT_CB>> 返回值创建后的initial_suspend的await_resume。可不是 co_wait这个运算符重载等待体的恢复,co_wait的恢复需要用 await_suspend的接口传入的协程句柄:resumer。即父协程。即父协程恢复co_wait的运算符重载这个可等待体!!!!
恢复执行task<server> 的initial_suspend的await_resume
这里是如何控制是initail_suspend的await_resume,而不是 co_wait那个等待体的waiter的resume?????
这个是通过不同协程句柄控制的。通过get_return_object创建的协程句柄,那就是整个协程的。initial_suspend是协程的暂停点,对协程句柄调用resume,那就是恢复到init这个第一个暂停点。这个可以说都是父协程。
co_wait的暂停点句柄是initial_suspend函数第一个参数那个句柄。是wait的句柄,对它调用resume,那就是恢复co_wait后面接着的协程,先恢复 suspend_resume,再到协程继续走。这个是在co_wait后边的,是子协程。
auto initial_suspend() noexcept {
struct InitialSuspendAwaiter {
constexpr void await_resume() const noexcept {}
然后继续执行后面流程。就是函数体了,终于可以执行 start_server 函数体里面内容了。
第一个暂停点结束,就进入协程函数体,从第一句开始执行:
start_server
template<concepts::ConnectCb CONNECT_CB>
Task<Server<CONNECT_CB>> start_server(CONNECT_CB cb, std::string_view ip, uint16_t port) {
addrinfo hints { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM };
addrinfo *server_info {nullptr};
auto service = std::to_string(port);
// TODO: getaddrinfo is a blocking api
if (int rv = getaddrinfo(ip.data(), service.c_str(), &hints, &server_info);
rv != 0) {
throw std::system_error(std::make_error_code(std::errc::address_not_available));
}
AddrInfoGuard _i(server_info);
int serverfd = -1;
for (auto p = server_info; p != nullptr; p = p->ai_next) {
if ( (serverfd = socket(p->ai_family, p->ai_socktype | SOCK_NONBLOCK, p->ai_protocol)) == -1) {
continue;
}
int yes = 1;
// lose the pesky "address already in use" error message
setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
if ( bind(serverfd, p->ai_addr, p->ai_addrlen) == 0) {
break;
}
close(serverfd);
serverfd = -1;
}
if (serverfd == -1) {
throw std::system_error(std::make_error_code(std::errc::address_not_available));
}
if (listen(serverfd, max_connect_count) == -1) {
throw std::system_error(std::make_error_code(static_cast<std::errc>(errno)));
}
co_return Server{cb, serverfd};
}
一路无话,直到 co_return server{cb, serverfd};
co_return server
这个表达式等价于 promise_type.return_value(server)
这个task类的co_return实现在:
class task
struct promise_type: CoroHandle, Result<R> {
的Result<R>中。这里会找 return_value()这个函数,返回result,即 task中的 struct promise_type继承了 Result<R>。
co_return 会到 Result<R>的 return_value,value参数就是 Server<...>。
template<typename R> // for promise_type
constexpr void return_value(R&& value) noexcept {
return set_value(std::forward<R>(value));
}
task<server..>协程要退出了,走到 task类的:
auto final_suspend() noexcept {
return FinalAwaiter {};
}
构造可等待对象 FinalAwaiter :
struct FinalAwaiter {
constexpr bool await_ready() const noexcept { return false; }
template<typename Promise>
constexpr void await_suspend(std::coroutine_handle<Promise> h) const noexcept {
if (auto cont = h.promise().continuation_) {
get_event_loop().call_soon(*cont);
}
}
constexpr void await_resume() const noexcept {}
};
又是ready是false,进入suspend:通过h.promise().continuation_取到父协程句柄,即task<void>那个,让它进入ready队列。注意正如上面红字标注,这个父进程句柄恢复的就是刚才 co_wait运算符重载的地方的等待。
同时这个server的final退出体就暂停了。!!!这个没看到哪里恢复FinalAwaiter ????FinalAwaiter的await_resume是不能再被调用了,因为协程已经co_return了。只能在退出时,对协程句柄做destroy,就会清理finalWait。
即这个任务:
运行到这里,即server socket申请后,暂停了。同时用:
if (auto cont = h.promise().continuation_) {
get_event_loop().call_soon(*cont);
}
恢复父协程以前暂停在co_wait start_server的地方:
刚才的co_wait就恢复了。执行到task的Awaiter中的await_resume()函数。
auto operator co_await() const && noexcept {
struct Awaiter: AwaiterBase {
decltype(auto) await_resume() const {
if (! AwaiterBase::self_coro_) [[unlikely]]
{ throw InvalidFuture{}; }
return std::move(AwaiterBase::self_coro_.promise()).result();
}
};
return Awaiter {handle_};
}
这里就是刚才那个对象,所以self_coro_是server task那个。
最终,调用Result<R>中的
constexpr T result() && {
if (auto exception = std::get_if<std::exception_ptr>(&result_)) {
std::rethrow_exception(*exception);
}
if (auto res = std::get_if<T>(&result_)) {
return std::move(*res);
}
throw NoResultError{};
}
result即server对象被移出通过在await_resume函数,
终于返回到
Task<void> amain() {
auto server = 上面返回的对象移动后,赋值给server;
终于走到 fmt::print("Serving on 127.0.0.1:8888\n");
然后如法炮制执行
Task<void> serve_forever() {
Event ev { .fd = fd_, .events = EPOLLIN };
auto& loop = get_event_loop();
std::list<ScheduledTask<Task<>>> connected;
while (true) {
co_await loop.wait_event(ev);
sockaddr_storage remoteaddr{};
socklen_t addrlen = sizeof(remoteaddr);
int clientfd = ::accept(fd_, reinterpret_cast<sockaddr*>(&remoteaddr), &addrlen);
if (clientfd == -1) { continue; }
connected.emplace_back(schedule_task(connect_cb_(Stream{clientfd, remoteaddr})));
// garbage collect
clean_up_connected(connected);
}
}
Task<void> serve_forever() 这个会导致新生成协程,
在 co_await loop.wait_event(ev);时,ev.fd=4, 是socket的文件句柄。
也会走一遍 co_await的运算符重载,位于task。wait_event只是有awaitable对象:没有co_wait重载
struct WaitEventAwaiter {
constexpr bool await_ready() const noexcept { return false; }
template<typename Promise>
constexpr void await_suspend(std::coroutine_handle<Promise> handle) noexcept {
handle.promise().set_state(Handle::SUSPEND);
event_.handle_info = {
.id = handle.promise().get_handle_id(),
.handle = &handle.promise()
};
selector_.register_event(event_);
}
void await_resume() noexcept { }
~WaitEventAwaiter() {
selector_.remove_event(event_);
}
Selector& selector_;
Event event_;
};
[[nodiscard]]
auto wait_event(const Event& event) {
return WaitEventAwaiter{selector_, event};
}
暂停实现里面, 传入的handle是刚才的新TAsk<void>, 它的fd是2. 也就是说 serve_forever()协程函数暂停了。这里等待的是一个可等待对象WaitEventAwaiter。
注册事件到selector里面。EPOLLIN事件,是否有客户端来连接,通过这个事件激活的。
void register_event(const Event& event) {
epoll_event ev{ .events = event.events, .data {.ptr = const_cast<HandleInfo*>(&event.handle_info) } };
if (epoll_ctl(epfd_, EPOLL_CTL_ADD, event.fd, &ev) == 0) {
++register_event_count_;
}
}
注册后都暂停了,一层层往外都返回,可以看下面的栈,最终回退到 run里面:
执行完ready的run函数后,run_once又循环。
这时由于没用其他事件,执行到
auto event_lists = selector_.select(timeout.has_value() ? timeout->count() : -1);
超时被设置成无限长,导致系统调用:
int ndfs = epoll_wait(epfd_, events.data(), register_event_count_, timeout);
epoll_wait永远被卡住:
整个应用是单线程,这时等于程序已经不运行了。
卡住等待客户端连接
除非客户端来连接,epoll_wait系统函数被激活,从系统的event事件里面取到以前保存的回调 handle_info,把他放入ready去执行。它是指向一个task的 promise_type的指针。从promise_type取到协程handler,执行在run里面执行resume:
for (auto&& event: event_lists) {
ready_.push(event.handle_info);
}
插入到ready队列开始调用task的run方法(根据刚才的handle_info,重新构造协程句柄,执行resume),激活刚才的wait点,从子协程不断向父协程恢复工作。
Task<void> serve_forever() {
Event ev { .fd = fd_, .events = EPOLLIN };
auto& loop = get_event_loop();
std::list<ScheduledTask<Task<>>> connected;
while (true) {
co_await loop.wait_event(ev); 这里被激活sockaddr_storage remoteaddr{};
socklen_t addrlen = sizeof(remoteaddr);
int clientfd = ::accept(fd_, reinterpret_cast<sockaddr*>(&remoteaddr), &addrlen);
if (clientfd == -1) { continue; }
connected.emplace_back(schedule_task(connect_cb_(Stream{clientfd, remoteaddr})));
// garbage collect
clean_up_connected(connected);
将echo_server实现的 connect_cb_(stream)这个协程函数,放入ready执行。
这里serve_forever自身可以继续无限循环,等待接收连接。
connect_cb_是handle_echo:传入的参数Stream{clientfd, remoteaddr}:
Task<> handle_echo(Stream stream) {
auto& sockinfo = stream.get_sock_info();
auto sa = reinterpret_cast<const sockaddr*>(&sockinfo);
char addr[INET6_ADDRSTRLEN] {};
auto data = co_await stream.read(100);
fmt::print("Received: '{}' from '{}:{}'\n", data.data(),
inet_ntop(sockinfo.ss_family, get_in_addr(sa), addr, sizeof addr),
get_in_port(sa));
fmt::print("Send: '{}'\n", data.data());
co_await stream.write(data);
fmt::print("Close the connection\n");
stream.close();
}
server socket分析完毕!
客户端连接代码分析
Task<> tcp_echo_client(std::string_view message) {
auto stream = co_await asyncio::open_connection("127.0.0.1", 8888);
fmt::print("Send: '{}'\n", message);
co_await stream.write(Stream::Buffer(message.begin(), message.end() + 1 /* plus '\0' */));
auto data = co_await asyncio::wait_for(stream.read(100), 300ms);
fmt::print("Received: '{}'\n", data.data());
fmt::print("Close the connection\n");
stream.close();
}
int main(int argc, char** argv) {
asyncio::run(tcp_echo_client("hello world!"));
return 0;
}
首先通过run协程tcp_echo_client,经过和上面分析一样的流程后,进入 open_connection:
Task<Stream> open_connection(std::string_view ip, uint16_t port) {addrinfo hints { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM };//AF_UNSPEC表示不限定地址族,允许返回 IPv4 (AF_INET
) 或 IPv6 (AF_INET6
) 地址。SOCK_STREAM
对应 TCP,SOCK_DGRAM
对应 UDP。addrinfo *server_info {nullptr};auto service = std::to_string(port);// TODO: getaddrinfo is a blocking apigetaddrinfo:函数会返回与主机名和服务名对应的多个地址,可能包含 IPv4 和 IPv6 地址。if (int rv = getaddrinfo(ip.data(), service.c_str(), &hints, &server_info);rv != 0) {throw std::system_error(std::make_error_code(std::errc::address_not_available));}AddrInfoGuard _i(server_info);server_info
是一个addrinfo
结构体链表的起始指针,链表中的每个节点代表一个潜在的服务器地址信息。通过遍历这个链表,你可以尝试连接到不同的地址,直到找到一个可用的为止。int sockfd = -1;for (auto p = server_info; p != nullptr; p = p->ai_next) {/*自己加的debug信息:char ipstr[INET6_ADDRSTRLEN];void *addr; std::string ipver;// Get pointer to the address (different fields in IPv4 and IPv6)if (p->ai_family == AF_INET) {// IPv4 struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;addr = &(ipv4->sin_addr); ip4从sin_addr取ipver = "IPv4";} else {// IPv6struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;addr = &(ipv6->sin6_addr); ip4从sin6_addr取ipver = "IPv6";}// ip转换成string打印inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr));std::cout << ipver << ": " << ipstr << std::endl;*/if ( (sockfd = socket(p->ai_family, p->ai_socktype | SOCK_NONBLOCK, p->ai_protocol)) == -1) {continue;}if (co_await detail::connect(sockfd, p->ai_addr, p->ai_addrlen)) { 去连接break;}close(sockfd);sockfd = -1;}if (sockfd == -1) {throw std::system_error(std::make_error_code(std::errc::address_not_available));}co_return Stream {sockfd};}去连接:
namespace detail {Task<bool> connect(int fd, const sockaddr *addr, socklen_t len) noexcept {int rc = ::connect(fd, addr, len);if (rc == 0) { co_return true; }这是连接成功,一般不会出现一连就成功的情况!if (rc < 0 && errno != EINPROGRESS) { 连接失败:throw std::system_error(std::make_error_code(static_cast<std::errc>(errno)));}这里大部分是 IO PENDING的状态:获取连接成功否,监听这个事件:EPOLLOUTEvent ev { .fd = fd, .events = EPOLLOUT };auto& loop = get_event_loop();co_await loop.wait_event(ev); 同server的分析,这里注册了EPOLLOUT事件。返回run的resume执行完了,开始runonce循环。执行到selector的select,进入epoll_wait,它的timeout被-1,无限等待。直到连接返回成功或者失败。int result{0};socklen_t result_len = sizeof(result);if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &result, &result_len) < 0) {// error, fail somehow, close socketco_return false;}co_return result == 0;}}
一个协程运行demo
协程运行演示代码
#include <iostream>
#include <coroutine>
size_t level = 0;
std::string INDENT = "-";
#include <iostream>
#include <coroutine>
struct SuspendAlways {
bool await_ready() noexcept {
std::cout << "SuspendAlways not ready." << std::endl;
return false; // Always suspend immediately
}
void await_suspend(std::coroutine_handle<>) noexcept {
// This function can be used for additional actions when the coroutine is suspended
std::cout << "SuspendAlways suspended." << std::endl;
}
void await_resume() noexcept {
// This function can be used for additional actions when the coroutine is resumed
std::cout << "SuspendAlways resumed." << std::endl;
}
};
struct SuspendNever {
bool await_ready() noexcept {
std::cout << "SuspendNever ready is true." << std::endl;
return true; // Always suspend immediately
}
void await_suspend(std::coroutine_handle<>) noexcept {
// This function can be used for additional actions when the coroutine is suspended
std::cout << "SuspendNever suspended." << std::endl;
}
void await_resume() noexcept {
// This function can be used for additional actions when the coroutine is resumed
std::cout << "SuspendNever resumed." << std::endl;
}
};
class Trace
{
public:
Trace()
{
in_level();
}
~Trace()
{
level -= 1;
}
void in_level()
{
level += 1;
std::string res(INDENT);
for (size_t i = 0; i < level; i++)
{
res.append(INDENT);
};
std::cout << res;
}
};
template <typename T>
struct sync
{
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
handle_type coro;
sync(handle_type h)
: coro(h)
{
Trace t;
std::cout << "Created a sync object" << std::endl;
}
sync(const sync&) = delete;
sync(sync&& s)
: coro(s.coro)
{
Trace t;
std::cout << "Sync moved leaving behind a husk" << std::endl;
s.coro = nullptr;
}
~sync()
{
Trace t;
std::cout << "Sync gone" << std::endl;
if (coro) {
std::cout << "Sync coro.destroy()" << std::endl;
coro.destroy();
}
}
sync& operator=(const sync&) = delete;
sync& operator=(sync&& s)
{
coro = s.coro;
s.coro = nullptr;
return *this;
}
T get()
{
Trace t;
std::cout << "We got asked for the return value..." << std::endl;
return coro.promise().value;
}
struct promise_type
{
T value;
promise_type()
{
Trace t;
std::cout << "sync Promise created" << std::endl;
}
~promise_type()
{
Trace t;
std::cout << "sync Promise died" << std::endl;
}
auto get_return_object()
{
Trace t;
std::cout << "sync::get_return_object: new sync" << std::endl;
return sync<T>{handle_type::from_promise(*this)};
}
auto initial_suspend()
{
Trace t;
std::cout << "sync: Started the coroutine, don't stop now!" << std::endl;
return SuspendNever{};
//std::cout << "--->Started the coroutine, put the brakes on!" << std::endl;
//return SuspendAlways{};
}
void return_value(T v)
{
Trace t;
std::cout << "sync: return_value " << v << std::endl;
value = v;
return;
}
auto final_suspend() noexcept
{
Trace t;
std::cout << "sync:final_suspend Finished the coro" << std::endl;
return SuspendAlways{};
}
void unhandled_exception()
{
std::exit(1);
}
};
};
template <typename T>
struct lazy
{
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
handle_type coro;
lazy(handle_type h)
: coro(h)
{
Trace t;
std::cout << "Created a lazy object" << std::endl;
}
lazy(const lazy&) = delete;
lazy(lazy&& s)
: coro(s.coro)
{
Trace t;
std::cout << "lazy moved leaving behind a husk" << std::endl;
s.coro = nullptr;
}
~lazy()
{
Trace t;
std::cout << "lazy gone" << std::endl;
if (coro) {
std::cout << "lazy coro.destroy()" << std::endl;
coro.destroy();
}
}
lazy& operator=(const lazy&) = delete;
lazy& operator=(lazy&& s)
{
coro = s.coro;
s.coro = nullptr;
return *this;
}
T get()
{
Trace t;
std::cout << "lazy: We got asked for the return value..." << std::endl;
return coro.promise().value;
}
struct promise_type
{
T value;
promise_type()
{
Trace t;
std::cout << "lazy: Promise created" << std::endl;
}
~promise_type()
{
Trace t;
std::cout << "lazy: Promise died" << std::endl;
}
auto get_return_object()
{
Trace t;
std::cout << "lazy:get_return_object new lazy" << std::endl;
return lazy<T>{handle_type::from_promise(*this)};
}
auto initial_suspend()
{
Trace t;
//std::cout << "Started the coroutine, don't stop now!" << std::endl;
//return SuspendNever{};
std::cout << "lazy:initial_suspend Started the coroutine, put the brakes on!" << std::endl;
return SuspendAlways{};
}
void return_value(T v)
{
Trace t;
std::cout << "lazy:return_value Got an answer of " << v << std::endl;
value = v;
return;
}
auto final_suspend() noexcept
{
Trace t;
std::cout << "lazy:final_suspend Finished the coro" << std::endl;
return SuspendAlways{};
}
void unhandled_exception()
{
std::exit(1);
}
};
bool await_ready()
{
const auto ready = this->coro.done();
Trace t;
std::cout << "Await " << (ready ? "is ready" : "isn't ready") << std::endl;
return this->coro.done();
}
void await_suspend(std::coroutine_handle<> awaiting)
{
{
Trace t;
std::cout << "coro.resume: About to resume the lazy" << std::endl;
this->coro.resume();
}
Trace t;
std::cout << "About to resume the awaiter" << std::endl;
awaiting.resume();
}
auto await_resume()
{
const auto r = this->coro.promise().value;
Trace t;
std::cout << "await_resume: Await value is returned: " << r << std::endl;
return r;
}
};
lazy<std::string> read_data()
{
Trace t;
std::cout << "Reading data..." << std::endl;
co_return "billion$!";
}
lazy<std::string> write_data()
{
Trace t;
std::cout << "Write data..." << std::endl;
co_return "I'm rich!";
}
sync<int> reply()
{
std::cout << "Started coro reply()" << std::endl;
std::string a = co_await read_data();
std::cout << "after coro read_data: Data we got is " << a << std::endl;
auto v = co_await write_data();
std::cout << "after coro write_data: write result is " << v << std::endl;
co_return 42;
}
int main()
{
std::cout << "Start main()\n";
auto a = reply();
return a.get();
}
代码说明参考:
协程是异步操作。它的意思是一个函数的执行,可以被中断;这时执行流程返回到调用者,继续执行。然后调用者在它认为合适的时候,可以恢复以前中断函数的地方,继续让那个函数执行,即执行流程会转入那个函数。也就是说调用者自身的流程中插入了那个继续的函数。那个函数执行完,return了。再继续调用者流程??
上面说的是单线程情况下。调用者负责协程的中断,恢复。即负责协程的执行管理,状态。
多线程时,可以在A线程中,mainThread调用协程coro,执行coro上半部分,然后中断coro。中断前将coro handle传到B线程;mainThread从调用点继续运行下面的。
B线程可以调用 coro handle的resume,使得coro在B线程继续运行。
协程类 Sync 或者 Lazy,我们叫他 Future。由于对于协程类Lazy,对它有co_wait操作,所以它需要有个awaitable的三个实现。其中 Future::await_suspend(coro_handle<> awaiting), awaiting保存起来,为了将来恢复执行 Future::await_resume。
在编译插入点,initial_suspend, final_suspend处,也会返回来两个 awaitable对象。这个是用coro协程句柄恢复的。
Future构建是的coro, 协程句柄是:std::coroutine_handle<lazy<std::string>::promise_type>,值为mytest.exe!read_data(void) #initial suspend 可以看到这个句柄模板参数是个promise_type。coro是可以通过 coro.promise()取到promise_type值。promise里面放的是协程的需要保存的东西, 比如值的获取this->coro.promise().value。 而awaiting协程句柄是 std::coroutine_handle<>,值 mytest.exe!reply(void) #suspend point 2 coro与awaiting,两个不是一回事
由于main不能启动await 协程。所以main先借助 reply()进入协程。
1,当执行 auto a = reply();时,
1.1 先创建 Sink<int> 类。
需要先创建对象 promise_type,然后到 promise_type::get_return_object 方法:
return sync<T>{handle_type::from_promise(*this)}; //通过promise_type实例获得 coro handle。将它作为 sync<int>构造函数的实参传入。生成了 Sink<int>对象。
即
using handle_type = std::coroutine_handle<promise_type>;
handle_type coro;
sync(handle_type h)
: coro(h){
}
1.2 进入编译器注入的第一个suspend点: promise_type::initial_suspend
struct SuspendNever {
bool await_ready() noexcept {
return true; // Always suspend immediately
}
void await_suspend(std::coroutine_handle<>) noexcept {
// This function can be used for additional actions when the coroutine is suspended
std::cout << "SuspendNever suspended." << std::endl;
}
void await_resume() noexcept {
// This function can be used for additional actions when the coroutine is resumed
std::cout << "SuspendNever resumed." << std::endl;
}
};
因为主线程过来的,所以不能在主线程中 await。所以这里是不让协程reply暂停的:return SuspendNever(). 返回了一个SuspendNever的对象实例。是个waitable。
这会导致他进入 struct SuspendNever的 SuspendNever::await_ready,返回了true,即不暂停。接着走到 SuspendNever::await_resume。啥也没干。await_resume后协程reply继续运行。
因为主线程调用的协程,main不是个协程,不能在main里面co_wait。所以这个Sync类自身没有实现Awaitable对象!
1.3 开始运行协程reply的函数体里面,到可暂停点协程co_wait read_data();
开始运行到协程reply的第一个语句打印后,接着走到 std::string a = co_await read_data();
原型:lazy<std::string> read_data()
这里先产生协程对象;再执行运算符 co_await expr协程对象!!!
1.3.1 构造协程read_data对象,即返回值 lazy<std::string>
与上面分析一样,首先创建返回对象Lazy<string>。先构造Lazy<string>::promise_type, 再通过 get_return_object 创建这个 read_data协程的handle。最终创建出返回对象 Lazy<string>实例。将刚才的read_data协程的句柄handle保存到这个对象里面。
1.3.1.1 进入第一个编译器插入的暂停点 initial_inspect
auto initial_suspend()
{
return SuspendAlways{};
}
通过这个返回的await对象:
struct SuspendAlways {
bool await_ready() noexcept {
return false; // Always suspend immediately
}
void await_suspend(std::coroutine_handle<>) noexcept {
std::cout << "SuspendAlways suspended." << std::endl;
}
void await_resume() noexcept {
std::cout << "SuspendAlways resumed." << std::endl;
}
};
先进入 SuspendAlways::await_ready, 返回false是要暂停,所以进入 SuspendAlways::await_suspend,返回void,就暂停住了。记住这个暂停点1.
1.3.2 由于上面暂停住了,返回调用者父协程reply,这时该执行 co_wait运算符了
这里Lazy的promise_type没有重载transform,而且Lazy自身没有重载运算符co_wait。所以默认返回的表达式就是 awaitable的。
真的Lazy是awaitable,它实现了那三个函数:lazy::await_ready,lazy::await_suspend ,lazy::await_resume。
所以才会先进入 lazy<std::string>::await_ready()
bool await_ready()
{
const auto ready = this->coro.done();
Trace t;
std::cout << "Await " << (ready ? "is ready" : "isn't ready") << std::endl;
return this->coro.done();
}
这里根据协程自身状态,由于协程 read_data刚执行到这里,not done。所以返回 ready就是false。not ready 就会进入 lazy::await_suspend:
这个调用的伪代码是:reply协程() co_wait调用的是.read_data协程的awaitable对象.await_suspend(参数是reply协程句柄)
void await_suspend(std::coroutine_handle<> awaiting)
{
{
this->coro.resume(); 记住这个恢复点。resume会导致流程走到coro协程read_data句柄的initial_suspend的waitable对象的SuspendAlways::await_resume.执行完流程还会返回这里。
......
走到 coro.resume()时,这个coro存的是read_data协程的句柄 Lazy。刚才协程coro在initial_suspend中被暂停了。这个coro.resume()会走到SuspendAlways::await_resume()。也就是协程继续执行,initial_suspend插入点的后续就是协程read_data的协程函数体。
(总结就是 co_wait express时,构建协程Lazy,暂停在initial_suspend的waitable对象。然后执行co_wait, 在Lazy的await_suspend,就恢复了刚才的暂停,开始执行函数体)
lazy<std::string> read_data()
{
Trace t;
std::cout << "Reading data..." << std::endl;
co_return "billion$!";
}
直到遇到 co_return "xxx";
这个会走到: lazy<std::string>::promise_type::return_value(std::string v)
void return_value(T v)
{
value = v;将这个 "billion$"保存到了 promise_type的value里面。也就是给承诺的值里面放值了
return ;
}
到这里协程就执行完了。
1.3.3 接着进入编译器插入的第二个暂停点 > lazy<std::string>::promise_type::final_suspend()
co_return后read_data协程执行完毕,进入final_suspend:
auto final_suspend() noexcept
{
return SuspendAlways{};
}
这里返回个一直暂停的await对象。
接着进入 SuspendAlways的await_ready,await_suspend,进入暂停状态。
1.4 read_data协程在final_suspend暂停后,返回到父协程reply调用者继续执行,父协程reply被恢复了!!!!!
即 lazy<std::string>::await_suspend(std::coroutine_handle<void> awaiting)中
void await_suspend(std::coroutine_handle<> awaiting)
{
this->coro.resume(); 上面返回到了这里,继续下一句了
}
Trace t;
std::cout << "About to resume the awaiter" << std::endl;
awaiting.resume();
}
注意的是:这时Lazy协程read_data已经执行完co_return,不能在resume了。
void await_suspend(std::coroutine_handle<> awaiting)
{
{
Trace t;
std::cout << "coro.resume: About to resume the lazy" << std::endl;
this->coro.resume();
}
this->coro.resume();//去恢复刚才的暂停,会异常
只有通过coro handle destroy才能销毁了。
走到 awaiting.resume(),这个 awaiting是reply协程的句柄,即它的read_data的父协程,即这个await对象自己的resume,它现在在await_suspend函数里面。那resume后就该进入它的 lazy<std::string>::await_resume。
这段伪代码编译器插入应该为:reply协程. lazy的await对象.await_resume()
coro是read_data协程自身的resume。激活的initial_suspend的 SuspendAlways ,进入 await_resume。这个是进入协程函数的开始执行。coro 是 std::coroutine_handle<promise_type>,它里面放着 promise_type。
awaiting 是 std::coroutine_handle<>这个 awaiting是reply协程的句柄 ,是父协程句柄。激活协程自身lazy实现的 await_resume。(这时协程已经执行完了co_return,在final_suspend处停住了。)
await_resume里面做些处理,比如返回值的获取。它的函数返回值 return r就是最终 co_wait expre 这个表达式的返回值!
1.4.1 进入 lazy<std::string>::await_resume()
auto await_resume()
{
const auto r = this->coro.promise().value;
Trace t;
std::cout << "Await value is returned: " << r << std::endl;
return r;
}
可以看到它是从 promise里面取的value。也就是上面那里放的值。到这里协程read_data的Lazy就彻底执行完毕了。
接下来父协程reply开始销毁Lazy
1.5 销毁 lazy
1.5.1 先析构 lazy
~lazy()
{
Trace t;
std::cout << "lazy gone" << std::endl;
if (coro)
coro.destroy();
}
coro.destroy();去销毁刚才 由于 final_suspend被暂停时,协程处于暂停状态。destroy去销毁它。已经不能resume了。
1.5.2 上面的destroy导致析构 lazy<std::string>::promise_type::~promise_type()
最终read_data协程就返回了。
1.6 执行reply的下一条语句
std::cout << "Data we got is " << a << std::endl;
最终输出 after coro read_data: Data we got is billion$!
1.7 co_await write_data(); 这个流程和读一模一样,只是改了字符串输出。
最终输出 after coro write_data: write result is I'm rich!
1.8 co_return 42;
先走到 Sync::return_value。
void return_value(T v)
{
Trace t;
std::cout << "sync: return_value " << v << std::endl;
value = v;
return;
}
> sync<int>::promise_type::return_value(int v)。同样是将值放入promise内的value字段保存。
sync<int> reply()
{
....
co_return 42;
}
int main()
{
std::cout << "Start main()\n";
auto a = reply();
return a.get();
}
由于这个是从main调用的,sync<int>类似个Future,它的值放在promise中。最终用 a.get()去获取。
co_return后,走到插入暂停点:sync<int>::promise_type::final_suspend() 。函数返回 return SuspendAlways{};执行里面的await_ready和await_suspend. 最终暂停协程Sync.
为什么是这里???lazy还有没死?返回调用者 Lazy的await_suspend最后一句:awaiting.resume();
void await_suspend(std::coroutine_handle<> awaiting)
{
{
Trace t;
std::cout << "coro.resume: About to resume the lazy" << std::endl;
this->coro.resume();
}
//this->coro.resume(); //不能打开。return的对象不能再resume。
Trace t;
std::cout << "About to resume the awaiter" << std::endl;
awaiting.resume(); 返回到这里,执行下一句
}
可能只是代码显示问题。
1.9 reply返回,a有了值。main获取协程里面的值:
a.get()
正是由于上面暂停了协程的return值 Sink<int>。所以这里去取它的值还是有的:
T get()
{
return coro.promise().value;
}
2.0,取完由于main要退出,执行:
析构Sink,里面释放刚才暂停住的coro协程句柄。
~sync()
{
Trace t;
std::cout << "Sync gone" << std::endl;
if (coro)
coro.destroy();
}
最后析构promise_type类。
从上面可以看到,子协程返回父协程时,自己就已经销毁。对于 co_wait read_data, 只是返回了值:
lazy<std::string> write_data()
std::string a = co_await read_data();//这里如果不是final_suspend暂停住了,就无法取协程里面的变量值了。
再如:sync<int> reply()
auto a = reply();
return a.get();也是暂停住了,可以取值。
补充
1, co_wait 协程函数()
协程函数最终返回一个可等待对象。它的执行流程如下:
如果将Lazy的initial_suspend返回值改成 SuspendNever
auto initial_suspend(){Trace t;std::cout << "Started the coroutine, don't stop now!" << std::endl;return SuspendNever{};//std::cout << "lazy:initial_suspend Started the coroutine, put the brakes on!" << std::endl;//return SuspendAlways{};}
协程执行流程超详细打印版
#include <iostream>
#include <sstream>
#include <coroutine>
class Trace
{
public:
static size_t level;
static std::string INDENT;
std::string res;
Trace()
{
in_level();
}
~Trace()
{
if (level == 0) {
std::cout << " level equals to 0!!!!!!!";
}
else
level -= 1;
}
std::string operator()() const {
return res + " ";
}
void in_level()
{
level += 1;
for (size_t i = 0; i < level; i++)
{
res.append(INDENT);
};
//std::cout << res;
}
};
void log(std::string s, const Trace& t) {
std::cout << t() << s << std::endl;
}
struct SuspendAlways {
std::string name;
SuspendAlways(std::string s = "") :name(s) {
Trace t;
log("4.0 Enter " + name + ":: create object.", t);
}
bool await_ready() noexcept {
Trace t;
log("4.1 Enter " + name + "::await_ready: Not ready", t);
return false; // Always suspend immediately
}
void await_suspend(std::coroutine_handle<> awaiting) noexcept {
Trace t;
log("4.2 Enter " + name + "::await_suspend: suspended.", t);
// This function can be used for additional actions when the coroutine is suspended
std::ostringstream oss;
oss << awaiting.address();
log( name + "::await_suspend: *** awaiting handle = " + oss.str()+" (same with coro handler)", t);
log("After initial_suspend::await_suspend, create co-routine object finish. return to caller main() ", t);
}
void await_resume() noexcept {
// This function can be used for additional actions when the coroutine is resumed
Trace t;
log("Enter " + name + "::await_resume.", t);
log("then will run co-routine body or co_await expre.", t);
}
};
struct SuspendNever {
bool await_ready() noexcept {
std::cout << "SuspendNever ready is true." << std::endl;
return true; // Always suspend immediately
}
void await_suspend(std::coroutine_handle<> coro_h) noexcept {
// This function can be used for additional actions when the coroutine is suspended
std::cout << "XXXXXXXXXXXXXXXXXXXXXXXXshould not show:SuspendNever suspended." << std::endl;
}
void await_resume() noexcept {
// This function can be used for additional actions when the coroutine is resumed
std::cout << "SuspendNever resumed." << std::endl;
}
};
template <typename T>
struct sync
{
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
handle_type coro;
sync(handle_type h)
: coro(h)
{
Trace t;
std::ostringstream oss;
oss << coro.address();
log("sync() constructor", t);
log("sync coro handler addr.="+oss.str(), t);
}
sync(const sync&) = delete;
sync(sync&& s)
: coro(s.coro)
{
Trace t;
log("Sync move copy", t);
s.coro = nullptr;
}
~sync()
{
Trace t;
log("~sync()", t);
if (coro) {
log("\tcoro.destroy()", t);
coro.destroy();
}
}
sync& operator=(const sync&) = delete;
sync& operator=(sync&& s)
{
coro = s.coro;
s.coro = nullptr;
return *this;
}
T get()
{
Trace t;
log("Enter sync::get(): return coro.promise().value", t);
return coro.promise().value;
}
void resume() {
Trace t;
log("====== under sync.resume() body=========", t);
std::ostringstream oss;
oss << coro.address();
log("sync::resume(): coro handler addr.=" + oss.str(), t);
coro.resume();
}
struct promise_type
{
T value;
promise_type()
{
Trace t;
log("sync::promise_type created", t);
}
~promise_type()
{
Trace t;
log("sync promise_type delete", t);
}
auto get_return_object()
{
Trace t;
log("sync::get_return_object: make sync object.", t);
auto coro_h = handle_type::from_promise(*this);
std::ostringstream oss;
oss << coro_h.address();
log("coro handler addr.=" + oss.str(), t);
return sync<T>{handle_type::from_promise(*this)};
}
auto initial_suspend()
{
Trace t;
log("sync::promise_type::initial_suspend(): Started the coroutine, put the brakes on!", t);
return SuspendAlways{ "Sync::promise_type::initial_suspend" };
}
void return_value(T v)
{
Trace t;
log("sync::promise_type::return_value ",t);
value = v;
return;
}
auto final_suspend() noexcept
{
Trace t;
log("sync::promise_type::final_suspend Finished the coro", t);
return SuspendAlways{ "Sync::promise_type::final_suspend" };
}
void unhandled_exception()
{
std::exit(1);
}
};
};
template <typename T>
struct lazy
{
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
handle_type coro;
lazy(handle_type h)
: coro(h)
{
Trace t;
log("3. Created a lazy object", t);
}
lazy(const lazy&) = delete;
lazy(lazy&& s)
: coro(s.coro)
{
Trace t;
log("lazy move copy.", t);
s.coro = nullptr;
}
~lazy()
{
Trace t;
log("Enter ~lazy() ", t);
if (coro) {
log("before coro.destroy()", t);
coro.destroy();
log("after coro.destroy()", t);
}
log("End ~lazy() ", t);
}
lazy& operator=(const lazy&) = delete;
lazy& operator=(lazy&& s)
{
coro = s.coro;
s.coro = nullptr;
return *this;
}
T get()
{
Trace t;
std::cout << "lazy: We got asked for the return value..." << std::endl;
return coro.promise().value;
}
void resume() {
Trace t;
log("6. lazy resume from initial_suspend.", t);
std::ostringstream oss;
oss << coro.address();
log("lazy coro handler = "+oss.str(), t);
coro.resume();
}
struct promise_type
{
T value;
promise_type()
{
Trace t;
log("1. lazy::promise_type() created", t);
}
~promise_type()
{
Trace t;
log("lazy:: ~promise_type()", t);
}
auto get_return_object()
{
Trace t;
log("2. lazy::get_return_object()", t);
auto coro_h = handle_type::from_promise(*this);
std::ostringstream oss;
oss << coro_h.address();
log("lazy::get_return_object(): lazy coro handler addr:"+oss.str(), t);
return lazy<T>{handle_type::from_promise(*this)};
}
auto initial_suspend()
{
Trace t;
log("4. enter lazy:initial_suspend: put the brakes on!", t);
return SuspendAlways{ "Lazy::promise_type::initial_suspend" };
}
void return_value(T v)
{
Trace t;
log("8. enter lazy::promise_type::return_value(). set co-return value on lazy::promise_type.value field.", t);
value = v;
return;
}
auto final_suspend() noexcept
{
Trace t;
log("9. Enter lazy::promise_type::final_suspend", t);
return SuspendAlways{"Lazy::promise_type::final_suspend"};
}
void unhandled_exception()
{
Trace t;
log("Enter unhandled_exception", t);
std::exit(1);
}
};
bool await_ready()
{
Trace t;
log("5. after init_suspend always_suspend, lazy<string> co-routine object created. run co_await expre ", t);
log("Enter lazy::await_ready()", t);
const auto ready = this->coro.done();
log(ready ? "It is ready" : "It isn't ready", t);
return this->coro.done();
}
void await_suspend(std::coroutine_handle<> awaiting)
{
Trace t;
log("Enter lazy::await_suspend()", t);
{
std::ostringstream oss;
oss << coro.address();
log("6. Lazy::await_suspend() in (co_await expr) waitable interface. lazy coro handler=" + oss.str(), t);
log("call coro.resume(), resume initial_suspend::await_suspend from read_data()/lazy<string>.", t);
this->coro.resume();
}
{
log("10. after read_data exec return_value and final_suspend. back to co_await Lazy::await_suspend", t);
std::ostringstream oss;
oss << awaiting.address();
log("awaiting hander = " + oss.str()+" (should be sync co-routine handler), then awaiting.resume", t);
log("so caller co-routie sync can also resume it. We can say a coro handler of a co-routine, can resume its sub-coroutine, or init/final suspend.", t);
awaiting.resume();
}
log("End lazy::await_ready()", t);
}
auto await_resume()
{
const auto r = this->coro.promise().value;
Trace t;
log("11. Lazy::await_resume() [by awaiting.resume()] Not: return value from promise_type ??for co-routine return statement. ", t);
log("for expre return value: auto a = co_await expre;",t);
log("then lazy can be deleted!",t);
return "lazy::await_resume set return value: "+r;
}
};
lazy<std::string> read_data()
{
Trace t;
log("============ read_data() body============", t);
log("After lazy::initial_suspend, enter func body.", t);
log("Enter read_data() ", t);
log("7. Reading data: I'm on work......", t);
log("co_return billion$!, co-routine will return.", t);
co_return "billion$!";
}
lazy<std::string> write_data()
{
Trace t;
log("============ write_data() body============", t);
std::cout << "Write data..." << std::endl;
co_return "I'm rich!";
}
sync<int> co_reply()
{
Trace t;
log("============ co_reply() body============", t);
log("Enter co_reply() body", t);
log("before read_data(). call [co_await read_data;] ", t);
auto a = co_await read_data();
log("after read_data()", t);
log("read_data return: " + a, t);
//auto v = co_await write_data();
//std::cout << "after coro write_data: write result is " << v << std::endl;
log("============ co_reply() end============", t);
log("sync co-return 42", t);
co_return 42;
}
size_t Trace::level = 0;
std::string Trace::INDENT = " ";
int main()
{
Trace t;
log("============ main() ============", t);
log("call: auto a = co_reply()", t);
log("co_reply() will create Sync<> object", t);
auto a = co_reply();
log("after co_reply()", t);
log("now we resume sync's initial_suspend::await_suspend", t);
a.resume();
log("after a.resume() in main(). call a.get()", t);
return a.get();
}
其他示例:
https://godbolt.org/z/jx6dzcr8W