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++ でマルチスレッドデザインパターン

C++ で Immutable パターン

マルチスレッドプログラミングにおいて何故排他制御が必要かと言うと、それは同じデータ(もしくはデータセット)に対して  read と write が同時に起こると不都合なケースがあるからです。

「それならデータを書き込めなくしてしまおう(排他制御のオーバーヘッドもなくなるし)」 と言うのが Immutable パターンです。

このパターンについてはあまり C++ かどうかは関係ないですが、具体的には setter を持たず getter オンリー、かつコンストラクト時にパラメータをすべて初期化するクラスを作ります。このクラスのインスタンスはコンストラクト以降、外部から状態を変更することができませんので排他制御なしに複数のスレッドから 読み放題になります。

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

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

C++03 では言語仕様として thread 機能が定義されておらず、プラットフォーム非依存のスレッド関係のコードを書こうとした場合 Boost.Thread などのライブラリを使うしかなかったのですが、ご存知の通り C++11 から thread 関係の機能が標準ライブラリに取り込まれたためより手軽にスレッドの機能が利用できるようになりました (といいつつ、完全にサポートしている処理系はまだないようですが…)

そこで結城浩氏の 「Java言語で学ぶデザインパターン入門 マルチスレッド編 (原著 Concurrent Programming in Java 他)」で紹介されているパターンを C++ でやったらどうなるか紹介してみたいと思います。(題材としている内容は書籍とは違います)

※リンクが無いところはまだ記事書いてないです。すみません…

C++er 向け、 C++ の型判定エクササイズ (1)

一般的な入門書には殆んど載っていない(と思われる) C++ の型にまつわる問題を少し…

EX.1  以下の typedef された t0 ~ t12 の型は一体何になるでしょうか。

typedef int t0;
typedef int* t1;
typedef int t2(int);
typedef int(t3)(int);
typedef int(*t4)(int);
typedef int(&t5)(int);
typedef int t6[3];
typedef int (t7)[3];
typedef int(*t8)[3];
typedef int(&t9)[3];
typedef int(&(*t10)(int))[3];
typedef int(*(&t11)(int))[3];
typedef int(*(&t12)(int))(int);
typedef int(Foo::*t13)(int);

— 答え —
t0: int型
t1: intへのポインタ型
t2: intを1つ引数にとり、戻り値がintの関数型
t3: t2 と同じ
t4: intを1つ引数にとり、戻り値がintの関数へのポインタ型
t5: intを1つ引数にとり、戻り値がintの関数の参照型
t6: 3つのintを要素に持つ配列型
t7: t6 と同じ
t8: 3つのintを要素に持つ配列へのポインタ型
t9: 3つのintを要素に持つ配列の参照型
t10: intを1つ引数にとり、戻り値が「3つのintを要素に持つ配列への参照」の関数へのポインタ型
t11: intを1つ引数にとり、戻り値が「3つのintを要素に持つ配列へのポインタ」の関数の参照型
t12: int を一つ引数にとり、戻り値が「intを1つ引数にとり、戻り値がintの関数へのポインタ」の関数の参照型
t13: intを1つ引数にとり、intを返すFooのメンバー関数へのポインタ型

EX.2  以下の new を含んだ式はそれぞれ何をしているでしょうか。

new int[3]
(new int)[3]
new (int [3])
new (int (*[3])())
new (int(*)[3])
new (int(*[3])[3])

— 答え —
new int[3] : int 型3つ分の領域を new
(new int)[3] : new した int 型の領域から offset 3 の場所に(不正)アクセス
new (int [3]) : int 型3つ分の領域を new
new (int (*[3])()) : 引数無しで int を返却する関数ポインタ型3つ分の領域を new
new (int(*)[3]) : 3つのintを要素に持つ配列型へのポインタを new
new (int(*[3])[3]) : 3つのintを要素に持つ配列型へのポインタ3つ分の領域を new

EX.1 の使用例
EX.2 の使用例(まだ書いてません…)

どうでしょう? 全部楽勝で答えられたアナタは多分中級者以上 !