0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

co_await这些协程时需要注意线程切换的细节

程序喵大人 来源:程序喵大人 作者:程序喵大人 2022-11-03 09:18 次阅读

bfc58fd8-5b13-11ed-a3b6-dac502259ad0.png

在异步操作里,如异步连接、异步读写之类的协程,co_await这些协程时需要注意线程切换的细节。

以asio异步连接协程为例:

classclient{
public:
client(){
thd_=std::thread([this]{
io_ctx_.run();
});
}

async_simple::Lazyasync_connect(autohost,autoport){
boolret=co_awaitutil::async_connect(host,port);#1
co_returnret;#2
}

~client(){
io_ctx_.stop();
if(thd_.joinable()){
thd_.join();
}
}

private:
asio::io_contextio_ctx_;
std::threadthd_;
};

intmain(){
clientc;
async_simple::syncAwait(c.async_connect());
std::cout<<"quit
";#3
}

这个例子很简单,client在连接之后就析构了,看起来没什么问题。但是运行之后就会发生线程join的错误,错误的意思是在线程里join自己了。这是怎么回事?co_await一个异步连接的协程,当连接成功后协程返回,这时候发生了线程切换。异步连接返回的时候是在io_context的线程里,代码中的#1在主线程,#2在io_context线程,之后就co_return 返回到main函数的#3,这时候#3仍然在io_context线程里,接着client就会析构了,这时候仍然在io_context线程里,析构的时候会调用thd_.join(); 然后就导致了在io_context的线程里join自己的错误。

这是使用协程时容易犯错的一个地方,解决方法就是避免co_await回来之后去析构client,或者co_await回来仍然回到主线程。这里可以考虑用协程条件变量,在异步连接的时候发起一个新的协程并传入协程条件变量并在连接返回后set_value,主线程去co_await这个条件变量,这样连接返回后就回到主线程了,就可以解决在io线程里join自己的问题了。

bfc58fd8-5b13-11ed-a3b6-dac502259ad0.png

还是以上面的异步连接为例子,需要对之前的async_connect协程增加一个超时功能,代码稍作修改:

classclient{
public:
client():socket_(io_ctx_){
thd_=std::thread([this]{
io_ctx_.run();
});
}

async_simple::Lazyasync_connect(autohost,autoport,autoduration){
coro_timertimer(io_ctx_);
timeout(timer,duration).start([](auto&&){});//#1启动一个新协程做超时处理
boolret=co_awaitutil::async_connect(host,port,socket_);//假设这里co_await返回后回到主线程
co_returnret;
}

~client(){
io_ctx_.stop();
if(thd_.joinable()){
thd_.join();
}
}


private:
async_simple::Lazytimeout(auto&timer,autoduration){
boolis_timeout=co_awaittimer.async_wait(duration);
if(is_timeout){
asio::error_codeignored_ec;
socket_.shutdown(tcp::shutdown_both,ignored_ec);
socket_.close(ignored_ec);
}

co_return;
}

asio::io_contextio_ctx_;
tcp::socketsocket_;
std::threadthd_;
boolis_timeout_;
};

intmain(){
clientc;
async_simple::syncAwait(c.async_connect("localhost","9000",5s));
std::cout<<"quit
";#3
}

这个代码增加连接超时处理的协程,注意#1那里为什么需要新启动一个协程,而不能用co_await呢?因为co_await是阻塞语义,co_await会导致永远超时,启动一个新的协程不会阻塞当前协程从而可以去调用async_connect。

当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::Lazyasync_connect(conststd::string&host,conststd::string&port){
co_returnco_awaitutil::async_connect(host,port);
}

async_simple::Lazytest_connect(){
boolok=co_awaitasync_connect("localhost","8000");
if(!ok){
std::cout<<"connectfailed
";
}

std::cout<<"connectok
";
}

intmain(){
async_simple::syncAwait(test_connect());
}

这个代码简单明了,就是测试一下异步连接是否成功,运行也是正常的。如果稍微改一下test_connect:

async_simple::Lazytest_connect(){
autolazy=async_connect("localhost","8000");
boolok=co_awaitlazy;
if(!ok){
std::cout<<"connectfailed
";
}

std::cout<<"connectok
";
}

很遗憾,这个代码会导致连接总是失败,似乎很奇怪,后面发现原因是因为async_connect的两个参数失效了,但是写法和刚开始的写法几乎一样,为啥后面这种写法会导致参数失效呢?

原因是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 协程函数就好了,不分成两行去执行协程。

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 变量
    +关注

    关注

    0

    文章

    613

    浏览量

    28360
  • 线程
    +关注

    关注

    0

    文章

    504

    浏览量

    19675

原文标题:C++ 使用协程需要注意的问题

文章出处:【微信号:程序喵大人,微信公众号:程序喵大人】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    RAKsmart服务器升级需要注意哪些细节

    RAKsmart是一家知名的IDC(互联网数据中心)服务提供商,为广大用户提供包括服务器托管、租用在内的多种服务。当您的业务需求发生变化或者技术进步导致现有配置不再满足要求时,对服务器进行升级就显得尤为重要。以下是进行RAKsmart服务器升级时需要注意的一些关键细节
    的头像 发表于 09-25 09:56 178次阅读

    畅玩《黑神话:悟空》,除了“官配”硬件还需要注意这些......

    畅玩《黑神话:悟空》,除了“官配”硬件还需要注意这些......
    的头像 发表于 08-30 14:58 427次阅读
    畅玩《黑神话:悟空》,除了“官配”硬件还<b class='flag-5'>需要注意</b><b class='flag-5'>这些</b>......

    bnc公头注塑需要注意什么

    德索工程师说道在BNC公头注塑过程中,需要注意多个方面以确保产品的质量和生产效率。以下是对这一过程中关键注意事项的详细阐述:   材料选择:根据BNC公头的使用环境和性能要求,选择合适的注塑
    的头像 发表于 08-22 08:53 236次阅读
    bnc公头注塑<b class='flag-5'>需要注意</b>什么

    共模电感选型参数需要注意哪些

    电子发烧友网站提供《共模电感选型参数需要注意哪些.docx》资料免费下载
    发表于 07-30 14:23 0次下载

    IR615S桥接AP,在相同SSID的AP间不能切换需要注意哪些设置呢?

    现场有多个AP,用相同的SSID,没有使用AC,现场IR615S WiFi桥接AP WiFi时,当连接的AP关闭后,不能切换到到其他AP上,需要注意哪些设置?
    发表于 07-25 06:38

    FPGA实现SDIO访问需要注意的问题

    FPGA实现SDIO访问时,需要注意以下几个关键问题和细节: 初始化过程: SDIO总线的初始化是确保FPGA与SD卡能够正常通信的第一步。这包括设置时钟频率、配置数据传输模式以及校验协议等
    发表于 06-27 08:38

    应用PLC需要注意哪些问题

    PLC(可编程逻辑控制器)作为现代工业控制的核心设备,其应用的广泛性和重要性不言而喻。然而,在应用PLC的过程中,也需要注意一系列问题,以确保PLC系统的稳定运行和高效控制。本文将结合实际应用经验,详细探讨应用PLC时需要注意的问题,并给出相应的解决策略和建议。
    的头像 发表于 06-17 11:29 556次阅读

    pcb电路板元件布局需要注意什么

    pcb电路板元件布局需要注意什么
    的头像 发表于 03-14 15:24 858次阅读

    智能手环设计需要注意哪些

    随着电子技术的高速发展,可穿戴设备逐渐火爆,其中之一是智能手环,作为现代可穿戴技术的热门产品之一,它集成了多种功能,如健康检测、运动跟踪、通知提醒等,为了实现这些功能,需要用上哪些电路模块,在设计时需要注意哪些?下面一起来看看吧
    的头像 发表于 02-25 09:34 934次阅读

    让激光位移传感器更精准,安装的时候需要注意这些细节

    和应用上也拥有丰富的经验。本期小明就来盘点一下,激光位移传感器在安装的时候根据被测物及环境需要注意的关键点。↑更多明治精密激光位移传感器资料点击图片获取1材料/颜色存在
    的头像 发表于 02-19 12:48 1159次阅读
    让激光位移传感器更精准,安装的时候<b class='flag-5'>需要注意</b><b class='flag-5'>这些</b><b class='flag-5'>细节</b>

    使用电容降压时都需要注意哪些?

    使用电容降压时都需要注意哪些? 电容降压是一种常见且广泛应用的电路降压方式,它可以将高电压降低至设定的较低电压,并且具有稳定、简便、高效、可靠等优点。然而,在使用电容降压时,我们需要注意一些关键
    的头像 发表于 02-02 15:27 573次阅读

    谈谈的那些事儿

    随着异步编程的发展以及各种并发框架的普及,作为一种异步编程规范在各类语言中地位逐步提高。我们不单单会在自己的程序中使用,各类框架如fastapi,aiohttp等也都是基于异步
    的头像 发表于 01-26 11:36 1111次阅读
    谈谈<b class='flag-5'>协</b><b class='flag-5'>程</b>的那些事儿

    LTM4630电源模块在多路并联时在pcb设计时需要注意哪些细节

    LTM4630电源模块在多路并联时在pcb设计时需要注意那些细节 比如在3路或者4路并联时在画pcb时走线需要注意那些地方,要加入对称设计和阻抗匹配吗, 如何才能做到并联均流效果最好, 请大家提出一些建议和指导,谢谢。
    发表于 01-05 08:07

    选购电容器时需要注意哪些参数

    选购电容器时需要注意的参数有很多,这些参数将直接影响电容器的性能和适用场景。首先,电容器的额定电压是一个非常重要的参数。
    的头像 发表于 01-03 18:13 1062次阅读

    电感选型需要注意哪些参数

    电子发烧友网站提供《电感选型需要注意哪些参数.docx》资料免费下载
    发表于 12-28 09:25 0次下载