C++20でコルーチンが実装された
これにより、goroutineやChannelがC++でも自然に実装できるようになったので実装した
現在できること
- ChannnelによるTask間の通信
- selectによる「どれか一つ実行」
sample
停止するまでフィボナッチ数列をChannnelに吐き出し続けるgoroutineについて考えて見る
https://wandbox.org/permlink/RrCZXMunzgCgc43e
元ネタ: https://go-tour-jp.appspot.com/concurrency/5
cfn::Task<> fibonacci(std::shared_ptr<cfn::Sender<MyStrategy,int>> ret_ch, std::shared_ptr<cfn::Recver<MyStrategy,int>> quit_ch){
int x=0;
int y=1;
for(;;){
//ret_chの送信かquie_chの受信が可能になるまで待機
auto [ret, quit] = co_await select(ret_ch->send(x), quit_ch->recv());
if(ret){
int next = x+y;
x=y;
y=next;
}else if(quit){
break;
}
}
}
cfn::Task<> example(){
auto [ret_send,ret_recv] = cfn::makeChannel<MyStrategy,int>(MyStrategy{},0);
auto [quit_send,quit_recv] = cfn::makeChannel<MyStrategy,int>(MyStrategy{},0);
spown_task(fibonacci(std::move(ret_send), std::move(quit_recv))); // SchedulerにPostされ非同期に実行される
for(int i=0;i<10;++i){
auto x = co_await ret_recv->recv();
std::cout<<*x<<std::endl;
}
quit_send.reset(); // close。このChannelから値を取り出そうとすると std::optional<int>型のstd::nulloptが取り出される
}
使い方
coffin/goroutine
を使うには以下が必要である。詳細は ライブラリのreadme参照
- Scheduler
- ようするにthread_poolとかboost::asio::io_context(io_service)である。マルチスレッドで動かなくてもいいなら
std::vector<std::function<void()>>
を実行していくだけのクラスでよい -
coffin/goroutine
がこれらを組み込んでいないのは、すでにSchedulerがいるアプリケーションに途中からでも組み込めるようにするためである
- ようするにthread_poolとかboost::asio::io_context(io_service)である。マルチスレッドで動かなくてもいいなら
- ChannelStrategy
- 上のSchedulerとChannelをうまくつないでやるためのclassを定義する必要がある
Task と Channel
-
cfn::task<T>
- 非同期処理を行うためのコルーチンである
- co_awaitとchannelを駆使してやっていく
- たぶん大体同じようなclassがC++23で標準に入る
-
cfn::makeChannel(Strategy,queue_size)
- Channelを生成する。正確にはChannelへの参照を持つ
std::shared_ptr<Sender<Strategy, value_type>>
とstd::shared_ptr<Recver<Strategy, value_type>>
が返る -
std::shared_ptr<Sender<Strategy, value_type>>
の参照カウントが0になり~sender
が呼ばれると、Channelはcloseになる
- Channelを生成する。正確にはChannelへの参照を持つ
cfn::Task<> example(){
auto [ret_send,ret_recv] = cfn::makeChannel<MyStrategy,int>(MyStrategy{},0);
auto [quit_send,quit_recv] = cfn::makeChannel<MyStrategy,int>(MyStrategy{},0);
spown_task(fibonacci(std::move(ret_send), std::move(quit_recv)));
for(int i=0;i<10;++i){
auto x = co_await ret_recv->recv();
std::cout<<*x<<std::endl;
}
quit_send.reset(); // close
}
Select
Channnel.send()|recv()
を受け取り,最初に実行可能になったもの1つを実行する。
戻り値として bool|std::optional
のtupleを返し、何が実行されたか、実行した結果何の値をrecv
したかを判断できるようにする
cfn::Task<> fibonacci(std::shared_ptr<cfn::Sender<MyStrategy,int>> ret_ch, std::shared_ptr<cfn::Recver<MyStrategy,int>> quit_ch){
int x=0;
int y=1;
for(;;){
auto [ret, quit] = co_await select(ret_ch->send(x), quit_ch->recv());
if(ret){
int next = x+y;
x=y;
y=next;
}else if(quit){
break;
}
}
}
ほか
- 細かいとこ等はreadmeを見てほしい
- バグや問題、提案等があったらgithubのissueに報告やPRしてもらえるとありがたい