C++ 20 协程 Coroutine(2,等待体)

网站建设3年前发布
61 0 0

明确说C++20的协程大部分概念还算清晰,就是yeild,然后外部利用句柄resume。对协程这个概念有了解的不应该有什么特别难以理解的地方。,但co_await awaiter比较让人疑惑。,上次我已经讲过,awaiter其实是是一个对象,一个接口实现,其3个接口函数是(详细解释请翻阅第一章):,不少代码的例子都是在await_suspend 函数中,直接把handle.resume(),就是说这些例子都是在挂起时就理解恢复了协程运行,这样的例子貌似什么异步的感觉都没有,没有体现任何异步操作的效果和优势。,这样co_await awaiter​能用来干啥就有点让我好奇了。我的直觉是等待体awaiter在await_suspend应该就是记录协程句柄,同时发起一个异步操作(比如用一个线程完成文件读写),然后在异步操作完成后,恢复协程的运行,告知协程读写的结果。,co_await awaiter的在未来应该会有很多种等待体,比如AIO,异步网络,异步读写数据库等。这也应该是未来C++协程重点反正发展地方。,这个问题先提前说一下,我曾经疑惑过。await_suspend接口的参数,其是调用其的外部协程的句柄。,但让我疑惑的是 std::coroutine_handle<>​ 里面模板参数理论应该是协程promise_type​承诺对象。不知道您理解这儿的麻烦没有,如果你要写一个通用的awaiter,那么难道都要使用模板?让使用者填写其协程对应的promise_type。这样开发者,使用者都麻烦。,后面我发现,如果只要你不使用对应的承诺对象,std::coroutine_handle<promise_type>::promise()​ 。参数类型写成std::coroutine_handle<>也没有问题(<>中为空,默认为void)。这样也可以适配各种协程。,co_await  可以呈现出不少形式,如果你才开始学你会比较疑惑。,co_await 调用 awaiter的接口。co_ret 是从awaiter 里面的await_resume 接口的返回值。,fun() 函数返回值是awaiter 对象,co_ret 是从awaiter 里面的await_resume 接口的返回值。,例子:尝试异步IO(有缺陷),我们尝试一些一个异步的读取文件的操作,封装在awaiter对象await_read_file​里面,在其await_suspend​接口中,我们尝试使用std::async发起了一个异步操作。然后等待返回结果。,协程返回值仍然是 coro_ret<T>​, 承诺对象还是coro_ret<T>::promise_type​,这个地方和前面的例子几乎没有差别,只是initial_suspend​返回的std::suspend_never{},表示协程在初始化后(刚刚进入时)不进行挂起操作。源代码地址请点击。,最后输出的信息记录是:,其实您可以已经发现了。这个实现虽然可以正常运行,但没有起到任何异步操作效果,因为await_suspend的接口虽然发起了异步操作std::async。但后面又进行了等待操作 result_ = fur_.get();,你可以认为虽然他发起了异步操作,整个主线程还是阻塞的,没有任何异步效果。,在部分文章例子代码中,他们会提出一些异步思路。,比如在异步执行的函数read_file 中去调用 coro_hdl.resume(); 在await_resume中执行result_ = fur_.get();效果如何呢?我们先贴出作出改进代码。,但这无疑是一个错误的改进。最后的输出结果要不就是崩溃,要不就是无法真正完成协程。,为什么???这儿又是因为可恶的多线程陷阱了。我们贴个时序图,您就会更加理解。,202303060142395125da5106ddb2b9fed8023c3d0e311e7a85ec633,您不能在另外一个线程中去恢复协程的运行。,切记,切记。,那应该如何修正,能异步操作,有能唤醒协程呢?方法还是有的,在我们发起std::aysnc 操作,得到一个std::future时,我们可以在主循环里面去等待std::future​,因为future可以等待很短的时间,也可以反复尝试。这样我们的代码主循环就一边等待(反复尝试),一边干点别的事情。,不过我也懒得把这个很丑的模型实现出来了。,这儿我们可以讨论一个问题,C++的异步模式,promise/future,async/future,都需要future在后面等待事情的完成。特别是在服务器类型的开发,这种方式并不好用。(我注明了服务器类型呀),首先看,每一个异步操作都(可能)需要启动一个线程,这个消耗过大,其次每一个future都需要等待,其实在设计上也很讨厌。如果你设计一个队列保存future,那么还需要将future和需要回调的操作绑定起来。,个人用不太惯,有高人指点一下?在服务器里面怎么,上面那个例子很初步,真正用起来很不爽,那么怎么设计能更加好的设计协程的异步IO。,首先我们回顾一下传统的libuv这类传统的AIO设计。,这类AIO都是通过一个请求消息队列传递请求给线程池,让线程池去真正干活。线程池干完活后,再将结果返回给一个应答消息队列。请求消息中有一个请求者的回调函数指针,随后又会回填给应答消息中。主循环会不断检查应答消息队列里面有没有消息,如果有应答消息,就从消息中取出回调函数调用之。,这种模型才是比较通用的服务器异步模型设计。这种模型也很容易结合到协程co_await awaiter设计中来。你只需要在回调函数里面激活挂起的协程就可以了。,做一个简单的时序图给大家。,202303060142116172c2b345981fd6461059db8b030888f3816a647,而如果你想用libuv封装,我估计还是改造一下libuv的代码。毕竟如果寄希望协程句柄透传回填回来。也需要消息结构进行改变。,至于代码,我自己的代码库zcelib/dev分支,aio目录下的代码有一个测试实现。因为涉及的面有不少(因为功能,代码写在好多CPP里面),只贴出部分说明一下吧。,本章讲解了一下 co_wait awaiter​;也讲了一下如何设计一个异步的awaiter。

© 版权声明

相关文章