LoginSignup
1
3

More than 3 years have passed since last update.

C++20 で goroutine/Channel みたいなことをやるライブラリ作った

Posted at

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がいるアプリケーションに途中からでも組み込めるようにするためである
  • 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になる
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してもらえるとありがたい
1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3