26
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C++Advent Calendar 2020

Day 23

C++の非同期処理の現在および未来のコルーチンの為のcppcoro

Last updated at Posted at 2020-12-31

C++20でコルーチンが言語コアに追加された。またC++23ではライブラリ機能が追加されるだろう

C++20しか使えない現在の非同期処理の実装の実用的な選択肢として、またC++23におけるコルーチンの実装の可能性の一つとして、cppcoroを眺めていく

なぜコルーチンはC++に入ったのか

まず一般的にC++erの夢というのは大体以下のような感じである

  1. 雑に書いても安全に速度が出てほしい
  2. 速度が出てほしい

そしてそれとは別に非同期処理をやりたいという話がある
非同期処理の解決法にはthread+mutexによる解決とstateパターンによる解決の2種類があるが、それぞれ以下のような問題がある

  • thread+mutexによる解決の問題
    • いわゆるC10k問題など、生成数に上限がある
    • lock(mutex等)は重い
  • stateパターンによる解決の問題
    • 書きづらい

そこでコルーチン

  • c10k: コルーチンであれば大量に生成しても大丈夫
  • lock: ロックフリーによる実装がそこそこ可能
    • lockが走る状況になったら処理を別のコルーチンに明け渡すだけなので、簡単なCPU命令だけで完結する
  • 記述がstateパターンに比べ比較的簡単

ここでC++erの夢を見直すと、コルーチンはその夢を叶えてくれそうな気がする

  • 雑に書いても安全に速度が出る
  • 速度が出る

なぜcppcoroか

現在C++20に入ってるコルーチンはコア機能のみで、ライブラリ的な機能は入っていない
しかしライブラリ機能がないだけならライブラリを実装すればよく、より良い解決策としては誰かが実装してくれたものを使えばよい。cppcoroはそれをかなえてくれる

またcppcoroをベースにしたC++23(?)のコルーチンの提案が出ている
まぁこれが入るかはわからないが、cppcoroっぽい何かしらがいずれC++に入るでしょう
cppcoroは未来のC++になる可能性がある

cppcoroの大事な概念

  • task
  • aync_mutex
  • scheduler

だいたい コルーチン版future・mutex・thread_poolのことだと理解してもらえるとわかりやすい。
C#などのasyncを知っていると話が早いかもしれない
これらについて解説する

task

mutexやネット通信処理などの非同期処理を扱っていくためのコルーチン

cppcoro::task<> add_item(std::string value)
{
  cppcoro::async_mutex_lock lock = co_await mutex.scoped_lock_async();
  values=42;
}

これをwhen_all (すべて実行していってすべて完了したら処理を戻す)などを使って非同期実行をやっていく

// get_recordはネット越しにデータを取ってくる関数とする
  auto [task1, task2, task3] = co_await when_all_ready(
    get_record(123),
    get_record(456),
    get_record(789)
);

async_mutex

コルーチン版mutex
std::mutexと同様に複数のthreadsから同じリソースに対し同時にアクセスされる場合、待ち処理をする
std::mutexと違うのは待ち処理を行う際、OSのlockを獲得せずコルーチンをに中断する点にある

動作原理
mutex内部に状態としてロックフリーstackがある
待ち処理が発生する場合stackに追加
mutexのlockが解除され次第stackにあるコルーチンをとりだし実行する

{
  auto lock = co_await mutex.scoped_lock_async();
  values = 42;
}//unlock

scheduler

UI threadやthread_poolなどなど適切なthreadでtaskを実行する

co_await schedule_on(thread_pool, get_value());

例えば「f(X)をthread_poolの複数のスレッドで実行する」は次のようなコードになる

auto [task1, task2, task3] = co_await when_all_ready(
  []()->cppcoro::Task<int>{co_await thread_pool; return f(1);},
  []()->cppcoro::Task<int>{co_await thread_pool; return f(2);},
  []()->cppcoro::Task<int>{co_await thread_pool; return f(3);}
);

まとめ

未来がどうなるかはわからないけど未来っぽいものはgithub からcloneできるぞ

$ git clone https://github.com/lewissbaker/cppcoro.git

おまけ

概念的にはそこまで重要じゃないしC++23で入る気もあんまりしないんだけど頑張って調べたから聞いてよ

###メッセージキュー支援について

LMAX disruptor にインスパイアされたbarrier制御がcppcoroにはある

LMAX disruptor とは?

  • LMAX Exchange社が開発したjavaの 高速なbloking queueライブラリ
  • LMAX Exchange社は金融系の企業で、そこが「なによりもメッセージングがくっそ遅い」という理由で汎用性を残しつつゴリゴリに高速化OSS化した
  • 金の力ってスゲー

高速化の秘密

  • リングバッファの活用によるmalloc回数の削減
  • ロックフリー
  • より少ないatomic操作
  • キャッシュラインの工夫によるfalse sharingの回避

barrierとは?

  • 特定のリソースへのアクセスを制御する機構
  • 「シングルthreadによる書きこみ」→「書き込まれるまでwait」→「複数threadによる読み込み」の明確な順序付けを連続で行える機構
  • std::barrier でググると幸せになれるかも

sequence_barrierとは?

  • indexでやっていくbarrier(コルーチン用)
  • これがあると無限長(2**64)のバッファに対し「index iまで書き込み済み」「iに書き込まれるまで待機」みたいな制御が簡単安全高速にできる

[single|multi]_producer_sequencer

  • sequence_barrirを2つ使って「書き込み済み」「書き込まれるまで待機」「読み込み済み」「読み込まれるまで待機」を制御することでsequence_barrierをリングバッファに適用できるようにする
  • これらを使うとそれなりに簡単に安全高速にメッセージングを実現できる
  • blockingはキューが枯渇したとき・キューがいっぱいになったときのみ起きるる。したがってリングバッファのサイズが十分であればblockingは比較的発生しづらくなる
26
20
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
26
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?