はじめに
本連載ではPOCO(C++ Portable Components)というオープンソースのC++用クラスライブラリを紹介してきました。今回は、POCO::Foundationライブラリの締めくくりとしてスレッド関連のクラスを説明します。応用例としては、Worker Thread(ワーカースレッド)パターンを用いたサンプルコードをとりあげます。ワーカースレッド・パターンは、参考資料で紹介されているデザインパターンです。
これまでの記事
- POCO::Netライブラリによる組み込みWebサーバの実装
- 5分で使えるLoggingフレームワーク - POCO::Foundation -
- テキスト処理もPOCOにおまかせ
- POCO流ファイル処理あれこれ
対象読者
オブジェクト指向を理解し、ネイティブC++のクラスライブラリを活用できる方を対象としています。
必要な環境
プラットフォーム
POCOは、多様なプラットフォームで動作させることが可能です。現時点でWindows、Mac OS X、Linux、HP-UX、Tru64、Solaris、QNXでの動作を保証しています。
コンパイラ
いわゆる標準C++のコードに対応したものが必要です。POCO内部でSTLを使用しています。サンプルコードは、Windows Vista環境で「Microsoft Visual C++ 2008 Express Edition」を用いてビルドし、動作の検証を行いました。
スレッドクラスの基本
スレッドとは、簡単に言うと、プロセスをさらに細分化した最小の実行単位です。1つのプロセス内で並列処理を行う際には、複数のスレッドを扱うこと(マルチスレッド化)を避けて通ることはできません。また、最近普及しているマルチコアのCPUでは物理的にスレッドを同時実行することが可能ですから、マルチスレッドをうまく活用すれば処理能力の大幅な向上が見込めます。
ただし注意すべき点があります。並列動作は直感的にイメージするのが難しいため、発見困難なバグを埋めこんでしまうことがあるのです。うっかり不具合を混入させないためにも、POCOのクラスなどを利用して、見通しのよいプログラムを書くことが大切だと言えます。
POCOのスレッドクラス
スレッドの扱い方はプラットフォームによって当然異なります。しかし、POCOではスレッドに関するプラットフォームの差異をクラスで吸収しているので、環境に依存しないコーディングが可能です。
以下の表では、POCO::Foundationライブラリ内から、スレッド関連(Threading)としてパッケージされているクラスのうち、主要なものをピックアップしました。
クラス名 | 概要 |
Condition | POSIX仕様のCondition Variablesをシミュレートしたクラス |
Runnable | スレッド本体用抽象クラス |
ErrorHandler | スレッド関連クラスで使う例外クラスのベースクラス |
Event | イベント同期クラス |
FastMutex | Mutexクラス(再帰で使えないが、その分高速) |
Mutex | Mutexクラス(再帰でも使える) |
RWLock | 読み書きロック用クラス |
ScopedLock | スコープ限定のロッククラス |
Semaphore | セマフォ処理クラス |
SynchronizedObject | 同期オブジェクト(イベント、Mutex)の親クラス |
Thread | スレッド管理クラス |
ThreadPool | スレッドプールを実装したクラス |
Timer | スレッドベースのタイマー処理クラス |
TimerCallback | タイマー処理で使うコールバック用テンプレートクラス |
スレッド処理の基本形
最も基本的なサンプルは次のようになります。
#include "stdafx.h" #include "Poco/Thread.h" #include "Poco/Runnable.h" #include <iostream> //Runnableクラスを継承したスレッド本体クラス class Worker: public Poco::Runnable { // スレッドのメイン処理 void run() { std::cout << "excute!" << std::endl; } }; int _tmain(int argc, _TCHAR* argv[]) { // workerスレッドの実体 Worker worker; // スレッド管理クラス Poco::Thread thread; // workerスレッド開始 thread.start( worker ); // スレッドの終了待ち thread.join(); return 0; }
スレッド本体の処理は、Poco::Runnable
クラスを継承したクラスに記述します。スレッドの生成や実行はPoco::Thread
クラスを用いて行います。Poco::Runnable
クラスは、ごくシンプルな抽象クラス(インターフェースクラス)で、ひとつの純粋仮想関数run()
しか定義されていません。
class Runnable { public: Runnable(); virtual ~Runnable(); virtual void run() = 0; };
Poco::Runnable
を継承するサブクラスでは、メンバ関数run()
をオーバーライドします。ここで記述する処理内容がスレッドのメインルーチンとなります。上記のサンプルでは、“excute!”という文字列を1回だけコンソールに出力して終えるようにしました。
スレッドの生成と開始は、Poco::Thread
クラスのstart()
で行います。
void start( Poco::Runnable &target );
start()
メソッドにPoco::Runnable
のサブクラスを引数として渡すと、スレッドが生成されます。スレッド内からは、サブクラスのrun
関数がコールバックの形で実行されます。メインスレッド側では、スレッドを起動した後は、それが終了するまでjoin()
関数を使って待機するようにします。join
関数は、引数で与えられたサブクラスのrun
関数が終わるまでブロッキングされ、制御を戻しません。
なお、スレッドの終了はスレッド自身が行うように記述する必要があります。起動した側からスレッドを終了させるのではなく、その終了を待つようにします。これが一般的なスレッド処理の基本フローとなります。