0
点赞
收藏
分享

微信扫一扫

c++协程注意


 客户{
:
客户(){
线=线程([]{
io环境_.();
});
}

简单异步::协程::懒<>异步连接( 主机, 端口){
=协待 工具::异步连接(主机,端口);//.1
协中 ;//.2
}

~客户(){
io环境_.停止();
(线.可合并()){
线.合并();
}
}

:
异网::io环境 io环境_;
线程 线;
};

(){
客户 c;
简单异步::协程::同步等待(c.异步连接());
输出<<"退出\n";//.3
}

该示例很简单,​​客户​​​在连接后就析构了,没什么问题.但是运行之后就会有​​合并​​​线程的错误,错误即在线程里​​合并​​​自己了.为什么?​​协待​​​异步连接的协程,当​​连接成功​​​后协程返回,此时切换了​​线程​​​.
​​​异步连接​​​返回时是在​​io环境​​​的线程里,代码中的​​.1​​​在主线程中,​​.2​​​在​​io环境​​​线程,之后就​​协中​​​返回到​​主​​​函数的​​.3​​​,此时​​.3​​​仍然在​​io环境​​​线程里,接着​​客户​​​就会析构了,此时仍然在​​io环境​​​线程里,析构时会调用​​线.合并();​​​,然后就有了在​​io环境​​​的线程里​​合并​​​自己的错误.
这是使用​​​协程​​​时容易犯错的地方,解决方法就是避免​​协待​​​回来之后去析构​​客户​​​,或​​协待​​​仍然返回到​​主线程​​​.这里可考虑用​​协程条件变量​​​,在​​异步连接​​​时发起​​新的协程​​​并传入​​协程条件变量​​​,并在​​连接返回​​​后​​置值​​​,主线程​​协待​​​该条件变量,这样​​连接返回​​​后就回到主线程了,就可解决在​​io​​​线程里​​合并​​自己的问题了.

增加超时处理.

 客户{
:
客户():套接字_(io环境_){
线=线程([]{
io环境_.();
});
}

简单异步::协程::懒<>异步连接( 主机, 端口, 时长){
协程计时器 计时器(io环境_);
超时(计时器,时长).开始([](&&){});
//.1,启动新协程,来超时处理
=协待 工具::异步连接(主机,端口,套接字_);//假设这里`协待`返回后回到`主线程`
协中 ;
}

~客户(){
io环境_.停止();
(线.可合并()){
线.合并();
}
}


:
简单异步::协程::懒<>超时(&计时器, 时长){
是超时=协待 计时器.异步等待(时长);
(是超时){
异网::错误码 忽略误码;
套接字_.关闭(传控::套接字::都关闭,忽略误码);
套接字_.关闭(忽略误码);
}

协中;
}

异网::io环境 io环境_;
传控::套接字 套接字_;
线程 线;
是超时_;
};

(){
客户 c;
简单异步::协程::同步等待(c.异步连接("本地主机","9000",5s));
输出<<"退出\n";#3
}

该代码增加了​​连接超时处理​​​的协程,注意​​.1​​​那里为何新启动​​协程​​​,而不能用​​协待​​​呢?因为​​协待​​​是阻塞语义,​​协待​​​会永远超时,启动​​新协程​​​不会​​阻塞​​​当前协程,从而可去调用​​异步连接​​​.
当​​​超时​​​超时时就关闭​​套接字​​​,此时​​异步连接​​​就会​​返回错误​​​然后​​返回​​​到调用者,这似乎可超时处理​​异步连接​​​了,但是​​该代码​​​有问题.假如​​异步连接​​​没有超时会怎样?没有超时的话就返回到​​主​​​函数了,然后​​客户​​​就析构了,当​​超时​​​协程​​恢复​​​回来时​​客户​​​其实已析构了,此时再去调用成员变量​​套接字关闭​​​访问,会有​​已析构对象​​​错误.
也许有人会说,那就再​​​协中​​​之前去取消​​计时器​​​不就好了吗?该办法也不行,因为取消​​计时器​​​,​​超时​​​协程并不会立即返回,仍然会存在​​访问​​已析构对象的问题.

正确做法应该是​​同步​​​两个协程,​​超时​​​协程和​​异步连接​​​协程需要同步,在​​异步连接​​​协程返回之前需要确保已完成​​超时​​​协程,这样就可​​避免​​​访问​​已析构对象​​​.
该问题其实也是​​​异步回调安全返回​​​的经典问题,​​协程​​​也同样会遇见该问题,上面提到的​​同步​​​两个协程是解决方法之一,另外一个方法就是就像​​异步安全回调​​​那样,使用​​shared_from_this​​.

简单异步::协程::懒<>异步连接( &主机, &端口){
协中 协待 工具::异步连接(主机,端口);
}

简单异步::协程::懒<>测试连接(){
=协待 异步连接("本地主机","8000");
(!){
输出<<"失败,";
}

输出<<"成功";
}

(){
简单异步::协程::同步等待(测试连接());
}
//改后.
简单异步::协程::懒<>测试连接(){
=异步连接("本地主机","8000");
=协待 ;
(!){
输出<<"失败";
}

输出<<"成功";
}

代码简单明了,就是测试​​异步连接​​​是否成功,运行也是正常的.如果如上稍微​​改一下​​​ 很遗憾,​​该代码​​会使连接总是失败,似乎很奇怪,后面发现​​原因​​是,因为​​异步连接​​的两个参数​​失效​​了,但是​​写法​​和刚开始的​​写法​​几乎一样,为啥​​后面​​该写法会使​​参数​​失效呢?
原因是​​协待​​协程函数时,其实做了两件事:
1,调用​​协程函数​​创建协程,该步骤会创建​​协程帧​​,把​​参数和局部变量​​拷贝到​​协程帧​​里;
2,​​协待​​执行​​协程函数​​;
再看​​auto lazy=异步连接("localhost","8000");​​,该代码调用​​协程函数​​创建了​​协程​​,此时​​拷贝​​到​​协程帧​​里面的是​​两个临时变量​​,该行​​结束​​时临时变量就析构了,下一行​​协待​​执行​​该协程​​时就会出现​​参数失效​​问题.

​协待 异步连接("localhost","8000");​​​这样为什么没问题呢,因为​​协程​​​创建和​​协程调用​​​都在​​一行内​​​完成的,​​临时变量​​​知道​​协程执行​​​之后才会失效,因此不会有问题.
问题本质其实是​​​C++​​​临时变量​​生命期​​​问题.使用​​协程​​​时稍微注意一下就好了,可把​​const std::string&​​​改成​​std::string​​​,这样就不会有​​临时变量​​​生命期问题了,如果不想改参数类型就​​协待​​​协程函数就好了,不要分成​​两行​​去执行协程.


举报

相关推荐

0 条评论