c++协程注意

原文
原文2
原文3

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

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

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

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

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

该示例很简单,客户在连接后就析构了,没什么问题.但是运行之后就会有合并线程的错误,错误即在线程里合并自己了.为什么?协待异步连接的协程,当连接成功后协程返回,此时切换了线程.
异步连接返回时是在io环境的线程里,代码中的.1在主线程中,.2io环境线程,之后就协中返回到函数的.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,这样就不会有临时变量生命期问题了,如果不想改参数类型就协待协程函数就好了,不要分成两行去执行协程.

posted @   zjh6  阅读(17)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示