ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

【c++20】协程与屏障

【c++20】协程与屏障

屏障std::barrier

#include<mutex>#include<iostream>#include<future>#include<barrier>std::mutex m;constintcnt=3;std::barrierb(cnt);// 创建一个barrier,需要3个线程到达同步点doubletotalScore=0;voidCalcExamScore(inti){m.lock();std::cout<<"读取第"<<i<<"个学生的成绩,并累加到总成绩\n";m.unlock();doublecur=i*10;totalScore+=cur;b.arrive_and_wait();// 到达同步点并等待m.lock();std::cout<<"第"<<i<<"学生的成绩:"<<cur<<" 班级平均成绩"<<totalScore/cnt<<"\n";m.unlock();}intmain(){std::vector<std::thread>threads;for(inti=0;i<cnt;++i){threads.emplace_back([i](){CalcExamScore(i);});}for(auto&t:threads){t.join();}return0;}

回调函数

std::barrier 的回调函数先于所有线程从 arrive_and_wait() 返回执行。回调执行完毕后,屏障重置,各线程才继续往下走。这个顺序是标准保证的。回调函数在某个arrive_and_wait所在的线程执行。
以下程序分五步:
一,各线程读取分数。
二,回调函数执行 计算排名。
三, 各线程输入志愿。
四,根据分数和志愿匹配学校。
五,查看录取结果。

std::mutex m;constintcnt=3;inttrun=0;autoon_phase_complete=[]()noexcept{if(0==trun){std::cout<<"计算排名\n";}else{std::cout<<"根据志愿匹配学校\n";}++trun;};std::barrierb(cnt,on_phase_complete);// 创建一个barrier,需要3个线程到达同步点doubletotalScore=0;voidCalcExamScore(inti){m.lock();std::cout<<"读取第"<<i<<"个学生的成绩,并累加到总成绩\n";m.unlock();doublecur=i*10;totalScore+=cur;b.arrive_and_wait();// 到达同步点并等待m.lock();std::cout<<"第"<<i<<"个学生的成绩:"<<cur<<" 班级平均成绩"<<totalScore/cnt<<"。输入自己志愿\n";m.unlock();b.arrive_and_wait();// 到达同步点并等待m.lock();std::cout<<"第"<<i<<"个学生被某院校录取\n";m.unlock();}intmain(){std::vector<std::thread>threads;for(inti=0;i<cnt;++i){threads.emplace_back([i](){CalcExamScore(i);});}for(auto&t:threads){t.join();}return0;}

门闩std::latch

门栓只能使用一次,多次后异常。
上面代码的屏障改成std::latch b(cnt)后,就异常;

协程

线程切换:需要陷入内核态,保存全部通用寄存器、更新页表、修改内核调度队列,资源开销大,频繁切换会严重损耗性能;
C++20 协程切换:纯用户态操作,仅保存少量寄存器、局部变量、协程帧、Promise、协程句柄等轻量数据,挂起 / 恢复成本极低。
核心结论:C++20 是编译器原生无栈协程,轻量、切换成本低,但协程内部禁止阻塞。
只要函数内部包含co_await、co_yield、co_return三个关键字任意一个,这个函数就属于协程函数:
co_await:核心关键字,用于主动暂停协程,等待异步操作完成后恢复;
co_yield:暂停协程,同时向外产出一个返回值,典型用于生成器;
co_return:终止协程生命周期,返回最终结果
如果是C++23则代码很简洁。

// #include <generator> C++23支持的generator#include<iostream>#include<coroutine>#include<exception>#include<utility>#include<cstddef>//Generator 模拟C++23的std::generatortemplate<typenameT>classGenerator{public:structpromise_type{T current_value;Generatorget_return_object(){returnGenerator{std::coroutine_handle<promise_type>::from_promise(*this)};}std::suspend_alwaysinitial_suspend()noexcept{return{};}std::suspend_alwaysfinal_suspend()noexcept{return{};}std::suspend_alwaysyield_value(T value){current_value=std::move(value);return{};}voidreturn_void(){}voidunhandled_exception(){std::terminate();}};classiterator{public:usingiterator_category=std::input_iterator_tag;usingvalue_type=T;usingdifference_type=std::ptrdiff_t;usingpointer=T*;usingreference=T&;iterator():handle_(nullptr){}explicititerator(std::coroutine_handle<promise_type>h):handle_(h){}iterator&operator++(){handle_.resume();if(handle_.done())handle_=nullptr;return*this;}voidoperator++(int){++(*this);}booloperator==(constiterator&other)const{returnhandle_==other.handle_;}booloperator!=(constiterator&other)const{return!(*this==other);}T&operator*(){returnhandle_.promise().current_value;}T*operator->(){return&handle_.promise().current_value;}private:std::coroutine_handle<promise_type>handle_;};iteratorbegin(){if(!handle_)returniterator{};handle_.resume();if(handle_.done())returniterator{};returniterator{handle_};}iteratorend(){returniterator{};}Generator(Generator&&other)noexcept:handle_(std::exchange(other.handle_,nullptr)){}~Generator(){if(handle_)handle_.destroy();}Generator(constGenerator&)=delete;Generator&operator=(constGenerator&)=delete;private:Generator(std::coroutine_handle<promise_type>h):handle_(h){}std::coroutine_handle<promise_type>handle_;};Generator<longlong>fibonacci(intcount){longlonga=0,b=1;for(inti=0;i<count;++i){co_yielda;autonext=a+b;a=b;b=next;}}intmain(){for(autonum:fibonacci(10)){std::cout<<num<<" ";// Output: 0 1 1 2 3 5 8 13 21 34}}

co_yield a; 执行完后,就执行std::cout << num。

多次调用

同一个函数执行结束前,可以执行另外一个同名协程函数,互不影响。通过返回值区分。

intmain(){autoret1=fibonacci(3);autoret2=fibonacci(3);autoit1=ret1.begin();autoit2=ret2.begin();std::cout<<*it1<<" "<<*it2<<"\n";++it1,++it2;std::cout<<*it1<<" "<<*it2<<"\n";++it1,++it2;std::cout<<*it1<<" "<<*it2<<"\n";}

0 0
1 1
1 1

连接服务端、发送消息、接受信息

协程分四步:一,异步连接服务端。二,异步发送消息。三,挂起线程。 四,同步接受消息并处理。协程两次执行完,第一次执行一二三,第二次执行四。
先将100个协程执行一次,执行完毕后,这些线程再执行一次。
由于网络回应时间占处理时间的绝大部分,故其它时间可忽略。其它时间忽略后:
使用协程,处理时间等于所有网站反应时间的最大值。
不使用协程,处理时间等于所有网站反应时间之和。

扩展阅读

计算几何为骨,排样优化为魂
作品一:亲士套料工具箱 作品二: 亲士CAD工具箱
经典文章推荐:二维排样
万物皆数学
查阅鄙人的博文,请点击博文下载学院导航
活到老,学到老。明朝中后期,大约50%的进士能当上堂官(副部及更高);能当上堂官的举人只有十余人。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。

测试环境

操作系统:win7 开发环境: VS2019C++17
或者 操作系统:win10 开发环境: VS2022C++17
如无特殊说明,本算法用**C++**实现。

返回列表