C++ で Balking パターン

Balking パターンとは「オブジェクトに対しての処理要求に対し、オブジェクトが特定の状態にある場合にのみ処理を行う(そうでない場合は何もしないで抜ける)」というパターンです。

例えば GUI システムでは多くの場合 main スレッドが UI のイベントループをハンドルしているため、lock 待ち等で main スレッドが待ち状態に入ると UI がフリーズしてしまいユーザビリティーの観点から好ましくありません。その場合 try lock のような仕組みを利用して確実に lock が取得できる場合のみ lock 取得処理を行う(そうでない場合はイベントループを回し、lock が取得できるようになるまで待つ)と言うことが良く行われます.

上記がここで取り上げた例の擬似コードになります.  パターンの適用例としてはあまり紹介されていないように思いますが、これも一種の Balking パターンだと思います.

C++ でマルチスレッドデザインパターン

広告

C++ std::regex_token_iterator で文字列を split (token 分解)

完全に boost ::tokenizer 相当と言うわけでは無いですが、C++11 から追加された正規表現ライブラリに std::regex_token_iterator  と言うイテレータが含まれており、これを文字列の split に利用する事ができます。

std::regex_token_iterator (cppreference.com)

詳細については上記のリンクを見ていただくとして、std::regex_token_iterator の基本的な使い方は以下のようになります。

上記の例を見て頂ければ分かると思いますが、コンストラクタの第4引数に正規表現でキャプチャした文字列のうちどのインデックスのものをイテレーション対象にするかを指定します。インデックスには 0 と -1 を指定することが可能で、0 を指定した場合はマッチした文字列全体が、-1 を指定すると対象の文字列全体からマッチした部分を取り除いた断片がイテレーションの対象になります。

この「マッチした部分を取り除いた断片をイテレーションする」と言う機能を利用すると split 処理を書く事ができ、例えば

のようになります。

ただ処理速度的にはアレだと思いますので、速度が要求される場面では従来から行われている愚直な処理の方がマッチするのではないかと思います。

 

C++ で Future パターン

Future とはスレッドで実行している処理との同期、スレッドからの結果の受領を行うためのパターンです。Future パターンは C++11 から std::future という形で標準ライブラリで提供されていますので、具体例については  cppreference.com  などをご参照下さい。

C++ でマルチスレッドデザインパターン

 

C++ で Worker Thread パターン

Worker Thread とは一般に生成、破棄コストの高いスレッドをプールして使い回すためのパターンで Thread Pool とも呼ばれます。 Worker Thread パターンは Producer-Consumter パターンに基づいており、キューで受け渡されるのがデータではなく実行可能な仕事を表現したオブジェクトである点が特徴です。

以下の例で Runnable は仕事の基底クラスであり、std::shared_ptr<Runnable> のキューを介してプールされているスレッドに仕事を依頼します。 クライアントは Runnable のサブクラスを作成し、インスタンスをスレッドプールに add する事で自分でスレッドをハンドルする事無く非同期に仕事を実行させる事ができます。

実際に使用してみると以下のようになります。 以下の例では WorkA, WorkB という異なる2つの内容の Runnable サブクラスのインスタンスを add しています。 仕事は Runnable で抽象化されていますので WorkA、 WorkB 以外にも任意の仕事を実行させる事が可能です。

Runnable のような基底クラスではなく特定の仕事クラスのキューを持たせる事も可能ですが、より汎用的にするためにこのようにするケースが多いように思います。また C++11 からは std::function が利用できますので、std::function のキューにするとより C++ っぽいかもしれません。

std::function 版の使用例です。

C++ でマルチスレッドデザインパターン

C++ で Producer-Consumer パターン

Producer-Consumer とはデータを生産する Producer スレッドとデータを消費する Consumer スレッドがキューを介してデータをやり取りするパターンです。

以下の例では、Producer 側からキューに追加された文字列を Consumer スレッドで取り出して標準出力に出力しています。

Producer スレッドはキューにデータを積む際に Consumer スレッドを起床させ、Consumer スレッドはキューが空になるとブロックされ sleep 状態に入ります。この動作は Garded Suspension パターンそのもので、Producer-Consumer パターンは下位レベルで Garded Suspension パターンを利用していると言えます。 (実際以下の例も Garded Suspension パターンで挙げた例と殆ど同じ内容ですが、スレッドが複数になっている点が異なります)

実際に使用すると以下のようになります。

今回は Producer 側はメインスレッド1つのみですが、当然 Producer 側が複数スレッドになるパターンもあり得ます。

ちなみに上記の例でもスレッドをお行儀良く終了させるために  Two-Phase Termination パターン を併用しています。

C++ でマルチスレッドデザインパターン

C++ で Two-Phase Termination パターン

Two-Phase Termination とは外部からスレッドに対して終了要求を送り、スレッドが自身のタイミングの良い所で処理を終える事で安全にスレッドの終了を行うためのパターンです。

C++ の std::thread ではスレッドを強制終了させるような手段は用意されていないようですが、一般に Two-Phase Termination を行わずに外部からスレッドを強制終了するなどした場合、データの不整合や、リソースリークなどが発生する可能性がありますので大変重要なパターンだと思います。

C++ でマルチスレッドデザインパターン

C++ で Guarded Suspention パターン

Guarded Suspention とは、ある条件が整うまでスレッドの実行を block するというパターンです。 C++ の標準ライブラリで condition variable が提供されていますので、そのまま Guarded Suspention パターンに利用することができます。

以下の例はキューに追加された文字列をスレッドで取り出して標準出力に出力する例です。 Printer というクラスが内部にスレッドを有しており、append で文字列が追加された際にスレッドを起床させて処理をさせます。スレッド側はキューが空になるとブロックされて sleep 状態に入ります。

実際に使用すると以下の用になります。

実装的な注意点として C++ ではメンバー変数の初期化は初期化リストの記述順ではなく宣言順に行われるため、上記の例では std::thread のメンバーの宣言は最後に行うようにする必要があります。スレッドの動作は初期化リストでコンストラクトされた直後に開始されるため、うっかりスレッドが触っているメンバー変数の宣言をスレッドインスタンス宣言の後に行ってしまうとタイミングによって未初期化変数へのアクセスが発生してしまいます。

ちなみに上記の例ではスレッドをお行儀良く終了させるために  Two-Phase Termination パターン を併用しています。

C++ でマルチスレッドデザインパターン