在异步操作里,如异步连接、异步读写之类的协程,co_await这些协程时需要注意线程切换的细节。
以asio异步连接协程为例:
class client {public: client() { thd_ = std::thread([this]{ io_ctx_.run(); }); } async_simple::lazy async_connect(auto host, auto port) { bool ret = co_await util::async_connect(host, port); #1 co_return ret; #2 } ~client() { io_ctx_.stop(); if(thd_.joinable()) { thd_.join(); } }private: asio::io_context io_ctx_; std::thread thd_;};int main() { client c; async_simple::syncawait(c.async_connect()); std::cout<
这是使用协程时容易犯错的一个地方,解决方法就是避免co_await回来之后去析构client,或者co_await回来仍然回到主线程。这里可以考虑用协程条件变量,在异步连接的时候发起一个新的协程并传入协程条件变量并在连接返回后set_value,主线程去co_await这个条件变量,这样连接返回后就回到主线程了,就可以解决在io线程里join自己的问题了。
还是以上面的异步连接为例子,需要对之前的async_connect协程增加一个超时功能,代码稍作修改:
class client {public: client() : socket_(io_ctx_) { thd_ = std::thread([this]{ io_ctx_.run(); }); } async_simple::lazy async_connect(auto host, auto port, auto duration) { coro_timer timer(io_ctx_); timeout(timer, duration).start([](auto&&){}); // #1 启动一个新协程做超时处理 bool ret = co_await util::async_connect(host, port, socket_);//假设这里co_await返回后回到主线程 co_return ret; } ~client() { io_ctx_.stop(); if(thd_.joinable()) { thd_.join(); } }private: async_simple::lazy timeout(auto &timer, auto duration) { bool is_timeout = co_await timer.async_wait(duration); if(is_timeout) { asio::error_code ignored_ec; socket_.shutdown(tcp::shutdown_both, ignored_ec); socket_.close(ignored_ec); } co_return; } asio::io_context io_ctx_; tcp::socket socket_; std::thread thd_; bool is_timeout_;};int main() { client c; async_simple::syncawait(c.async_connect(localhost, 9000, 5s)); std::cout< 当timeout超时发生时就关闭socket,这时候async_connect就会返回错误然后返回到调用者,这看起来似乎可以对异步连接做超时处理了,但是这个代码是有问题的。假如异步连接没有超时会发生什么?没有超时的话就返回到main函数了,然后client就析构了,当timeout协程resume回来的时候client其实已经析构了,这时候再去调用成员变量socket_ close将会导致一个访问已经析构对象的错误。
也许有人会说,那就在co_return之前去取消timer不就好了吗?这个办法也不行,因为取消timer,timeout协程并不会立即返回,仍然会存在访问已经析构对象的问题。
正确的做法应该是对两个协程进行同步,timeout协程和async_connect协程需要同步,在async_connect协程返回之前需要确保timeout协程已经完成,这样就可以避免访问已经析构对象的问题了。
这个问题其实也是异步回调安全返回的一个经典问题,协程也同样会遇到这个问题,上面提到的对两个协程进行同步是解决方法之一,另外一个方法就是使用shared_from_this,就像异步安全回调那样处理。
还是以异步连接为例:
async_simple::lazy async_connect(const std::string &host, const std::string& port) { co_return co_await util::async_connect(host, port);}async_simple::lazy test_connect() { bool ok = co_await async_connect(localhost, 8000); if(!ok){ std::cout< async_simple::lazy test_connect() { auto lazy = async_connect(localhost, 8000); bool ok = co_await lazy; if(!ok){ std::cout< 原因是co_await一个协程函数时,其实做了两件事:
调用协程函数创建协程,这个步骤会创建协程帧,把参数和局部变量拷贝到协程帧里;
co_await执行协程函数;
回过头来看auto lazy = async_connect(localhost, 8000); 这个代码调用协程函数创建了协程,这时候拷贝到协程帧里面的是两个临时变量,在这一行结束的时候临时变量就析构了,在下一行去co_await执行这个协程的时候就会出现参数失效的问题了。
co_await async_connect(localhost, 8000); 这样为什么没问题呢,因为协程创建和协程调用都在一行完成的,临时变量知道协程执行之后才会失效,因此不会有问题。
问题的本质其实是c++临时变量生命周期的问题。使用协程的时候稍微注意一下就好了,可以把const std::string&改成std::string,这样就不会临时变量生命周期的问题了,如果不想改参数类型就co_await 协程函数就好了,不分成两行去执行协程。
10GBase-T合规性和恶劣环境的影响
粮食谷物玉米赤霉烯酮分析仪的用途有哪些
oltus-XFi定制电源完整性解决方案加速EM-IR分析
通过对比学习的角度来解决细粒度分类的特征质量问题
MWC上那些亮相的经典手机
co_await这些协程时需要注意线程切换的细节
变频错相控制方法中的时序管理设计与思考
AI根据面部特征就能判断一个人是直是弯,准确度惊人
麻省理工学院展望小型雷达传感器的未来
研发人员研发了一种非线性光学技术与适用的领域
全球十大最有价值和十大最强大的电信基础设施品牌
5分钟充满电的智能超快充技术原理揭秘
虚拟现实市场前景大好 但仍有不少困难需要克服
祝贺!又获一项专利证书
五月微软发布会:又跳票?Surface Pro 5和Surface Phone都没了
微变电阻是什么?微小电阻测量方法
DHT-11温湿度传感器介绍
芯经验——常用存储器形式
ADI推出DS28E30 1-Wire ECDSA安全认证器
设计节能螺线管驱动器