サンプルコードの解説
WorkNotificationクラス
Poco::Notification
を継承したWorkNotification
は、ワーカースレッドへのメッセージを示すクラスです。内部ではコマンド(仕事)の処理も記述しています。サンプルではコマンド処理を1種類しか定義していませんが、通常のプログラムでは複数のコマンドを用意することが多いでしょう。その場合は、execute()
を仮想関数としてクラスを継承し、各コマンド処理をオーバーライドして記述するようにします。
Poco::Random
クラスは、疑似乱数を生成するクラスです。Park-Miller
のアルゴリズムを使っているので、質的な問題があると言われているランタイム関数のrnd
関数より、一様な乱数を生成すると思われます。
サンプルコードのコマンド処理は、乱数で求めた時間だけsleep
を行う、というものです。
Workerクラス
ワーカースレッドクラスです。通知キューから通知オブジェクトを取得し、通知に記述してある処理を呼びだします。通知キューが空の場合は、取得できるまで待機(ブロッキング)します。この処理を無限ループで繰り返すようにしています。ただし、待機解除命令(後述)が出されると待機から制御が戻り、そこで終了となります。
以下の、run()
関数の最初の部分を見てください。
Poco::AutoPtr<Poco::Notification> pNf(_queue.waitDequeueNotification());
これは、わかりやすく書くと次の記述と同値です。元の記述は、Poco::AutoPtr
クラスにより、解放処理を省いているのです。
Poco::Notification* pNf = _queue.waitDequeueNotification(); if ( pNf ) { (省略) } else break; delete pNf;
waitDequeueNotification()
で、キューからの通知オブジェクトの取り出しとデータ待機を行っています。キューが空の場合、処理はブロッキングされているのですが、外部のスレッドから待機解除命令が出されると、待機をやめてNULL
を返します。待機解除命令は、Poco::NotificationQueue
クラスのwakeUpAll()
関数によって行います。なお、wakeUpAll()
関数はThreadPool
クラスで管理している全スレッドに対しての命令となります。
通知オブジェクトを取得した後は、自分のスレッド名と通知オブジェクトの番号をコンソールに出力します。なお、出力処理を行う際は、同期オブジェクトMutex
を使ったPoco::FastMutex
を用いて排他ロックをかけています。これは、複数のスレッドからの同時コンソール出力によって、表示が乱れてしまうことを防ぐためです。具体的には、Poco::FastMutex
をstaticなメンバ変数として、各スレッド間の排他処理に利用しています。また、Poco::FastMutex::ScopedLock
を使うことにより、そのロックオブジェクトが生存している{}のスコープに限定した排他制御になっています。
最後に、execute()
でコマンドの実行を行っています。
メインスレッド
_tmain
に記述したメインスレッドの処理はコメントを追っていけば理解できると思いますが、少し補足しておきます。Poco::ThreadPool
クラスのコンストラクタでは、保持する最小のスレッド数、最大のスレッド数、待機時間を指定できます。デフォルトでは、次のようになっています。
ThreadPool( int minCapacity = 2, int maxCapacity = 16, int idleTime = 60 );
maxCapacity
最大数を超えてワーカースレッドをstart()
関数で登録しようとすると、例外が発生します。最大スレッド数は、addCapacity(int n)
関数で後から増やすこともできます。
idleTime
スレッドが待機中になってからidleTime
秒を超えた場合、ThreadPool
クラスはそのスレッドを強制的に終了させます。ただし、指定した最小数より保持しているスレッドが少なくなることはありません。
スレッドの終了処理は、まず全ワーカースレッドへ終了指示を送り、その後全てのスレッドが終了するまで待機する、という「2段階の終了」となっています。このパターンのことを、「Two-Phase Termination」パターンと呼びます。
ThreadPool
クラスの詳細については、Webサイトのドキュメントを参照してください。
シーケンス図
Poco::NotificationQueue
に対するPoco::Notification
オブジェクトの追加・取得処理と、Worker
クラスでのexecute()
処理の部分をシーケンス図にしてみました。
実行画面
サンプルコードを実行すると、次のように50行分の表示が行われます。3つのワーカースレッドで、50個の仕事を手分けして処理している様子がイメージできるでしょうか。
Worker-1 excute No.0 Worker-3 excute No.1 ~(省略)~ Worker-2 excute No.49
さらに
サンプルに手を加えて、ワーカースレッドを増やしてみてください。大抵の環境では、処理が早くなるはずです。
まとめ
今回は、スレッド関連のクラスをちょっと駆け足で紹介しました。マルチスレッドはかなり奥の深い技術ですし、POCOのクラスも非常に多くの機能をサポートしています。限られた紙面ではとても全てを紹介しきれませんので、興味を持たれた方はぜひ原文のドキュメントやライブラリのソースを参考にして、活用してみてください。
参考資料
- 『増補改訂版Java言語で学ぶデザインパターン入門マルチスレッド編』結城浩 著、ソフトバンク クリエイティブ、2006年3月
- 『POCO C++ Libraries Reference』