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_await asyncio::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 对象以及每个协程实例是一一对应的。

C++20协程原理和应用 - 知乎 (zhihu.com)

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 api
getaddrinfo:函数会返回与主机名和服务名对应的多个地址,可能包含 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 {
// IPv6
struct 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的状态:获取连接成功否,监听这个事件:EPOLLOUT
    Event 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 socket
        co_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_suspendSuspendAlways ,进入 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{};
        }
这时,read_data协程创建Lazy这个fucture对象后,因为initial_suspend不等待,就直接执行read_data协程的函数体了。
在执行完函数体后,进入 final_suspend,这时由于返回 SuspendAlways,会暂停当前read_data协程。返回到reply,开始执行 co_wait 运算符。进入 lazy的await_ready.由于这里coro.done是true。那就不执行lazy::await_suspend. 直接进入resume,reply这个父协程就举行执行了。
 
2,co_wait 可等待对象;
这里就没有执行协程函数体的流程了。直接进入了可等待对象的 await_ready........
 
上面可以改成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

posted @ 2024-04-23 14:39  Bigben  阅读(71)  评论(0编辑  收藏  举报