メインのCThreadクラス
関数 | 説明 |
void CThread() | コンストラクタ。オブジェクトデータを初期化してスレッドを開始する |
void ~CThread() | スレッドが実行中の場合は終了し、リソースを解放する |
BOOL Event(LPVOID lpvData) | イベントスタック/キューにデータブロックを格納し、データが処理待機中であることをオブジェクトのスレッドに通知する |
BOOL Event(CTask *pTask) | イベントスタック/キューにCTaskオブジェクトを格納し、タスクが実行待機中であることをオブジェクトのスレッドに通知する |
int GetEventsPending() | イベントスタックで待機中のイベントの数を返す |
ThreadId_t GetId() | オブジェクトのスレッドIDを返す |
DWORD GetErrorFlags() | オブジェクトのエラーフラグを返す。エラーがない場合は、値0(NO_ERRORS)を返す。エラーがある場合は、MUTEX_CREATION(ミューテックスオブジェクトを作成できなかった)、EVENT_CREATION(イベントオブジェクトを作成できなかった)、THREAD_CREATION(オブジェクトのスレッドを作成できなかった)、ILLEGAL_USE_OF_EVENT(インターバルベースのスレッドに対してEventメンバ関数が呼び出された)のいずれか1つまたは複数のフラグが設定される |
BOOL PingThread(DWORD dwTimeoutMilli) | オブジェクトのスレッドが実行中かどうかを判別する。スレッドが実行中の場合はTRUEを、実行中でない場合はFALSEを返す。タイムアウトはミリ秒単位である |
SetPriority(DWORD dwPriority) | スレッドの優先度を設定する。Windowsでのみ使用可能 |
BOOL Start() | オブジェクトのスレッドを開始する |
BOOL Stop() | オブジェクトのスレッドを停止する |
void SetIdle(DWORD dwIdle) | スレッドのアイドル時間をミリ秒単位で変更する。インターバルベースのスレッド処理で使用する |
SetThreadType(ThreadType_t typ,DWORD dwIdle) | スレッドの種類をThreadTypeEventDrivenとThreadTypeIntervalDrivenとで変更する |
m_mutex | 同期に使うミューテックスオブジェクト。CMutexClassを参照 |
ThreadState_t ThreadState() | スレッドの状態を返す。ThreadStateBusy(スレッドはイベントを処理中)、ThreadStateWaiting(スレッドは新しいイベントを待機中)、ThreadStateDown(スレッドは実行中ではない)、ThreadStateShutingDown(スレッドはシャットダウンの途中)のいずれか |
ここまでサポートクラスについて見てきました。次はいよいよ、主役となるCThreadクラスの出番です。CThreadクラスは2種類のスレッドをサポートしています。1つはイベントドリブンのスレッド、もう1つはインターバル(間隔)ドリブンのスレッドです。イベントドリブンのスレッドとは、イベントオブジェクトに基づいてブロックされ、待ち状態が続くスレッドです。イベントオブジェクトの状態が非シグナル状態からシグナル状態に変わるまでは待ち状態が続きます。新しいイベントが発生するのは、別のスレッドがCThreadオブジェクトのキューにタスクを格納し、そのオブジェクトのスレッドに通知した(イベントオブジェクトをシグナル状態に設定した)ときです。シグナル状態になると、スレッドが起き、イベントキューからタスクをポップします。キューが空になるまでこれを続けます。
CThreadオブジェクトはタスクごとにOnTaskメンバ関数を呼び出します。タスクは先着順(FCFS:First Come First Serve)で処理されます。つまり、CThreadオブジェクトのキューに最初に格納されたタスクがまず処理され、次に2番目のタスクが処理され、という形です。キューへのアクセスはミューテックスオブジェクトによって同期されます。このため、スレッドが前のイベントを処理している最中に、別のイベントをキューに配置できます。キューが空になると、スレッドはイベントオブジェクトを非シグナル状態にリセットし、イベントオブジェクトの待ち状態に戻ります。CThreadクラスは2種類のイベントドリブンスレッドをサポートしています。細分型スレッドと非細分型スレッドです。詳細についてはCTaskを参照してください。
細分型のスレッドを実装するには、CThreadクラスから新しいクラスを派生する必要があります。派生クラスでは、そのオブジェクトのデータ型を処理するようにOnTaskの実装を再定義します。
#include "Thread.h" class CIncrementThread : public CThread { public: int counter; virtual BOOL OnTask( LPVOID lpv ) { ThreadId_t id; GetId(&id); if( lpv ) { int *pInt = (int *)lpv; //don't use cout here, output could be broken up due to //threading printf("\tthread(%ld, counter+%d=%d, counter incremented\n", id,*pInt,(counter+=*pInt)); } return TRUE; } virtual BOOL OnTask() { ThreadId_t id; GetId(&id); //don't use cout here, output could be broken up due to //threading m_mutex.Lock(); // protect the counter variable printf("\tthread(%ld, counter++= %d, counter incremented)\n", id,(++counter)); m_mutex.Unlock(); return TRUE; } int GetValue() { int counterValue = 0; m_mutex.Lock(); // protect the counter variable counterValue = counter; m_mutex.Unlock(); return counter; } void Reset() { m_mutex.Lock(); counter = 0; m_mutex.Unlock(); } CIncrementThread(){counter=0;} ~CIncrementThread(){} }; int main( int argc, char *argv[]) { // object allocated and thread started CIncrementThread MyThread; int two=2; while( MyThread.GetValue() < 20 ) { MyThread.Event(); // increment value by one Sleep(100); // pauses the root thread for 100 // milliseconds } MyThread.Reset(); while( MyThread.GetValue() < 40 ) { MyThread.Event(&two); Sleep(100); } }
thread(5220, counter++= 1, counter incremented) thread(5220, counter++= 2, counter incremented) thread(5220, counter++= 3, counter incremented) thread(5220, counter++= 4, counter incremented) thread(5220, counter++= 5, counter incremented) thread(5220, counter++= 6, counter incremented) thread(5220, counter++= 7, counter incremented) thread(5220, counter++= 8, counter incremented) thread(5220, counter++= 9, counter incremented) thread(5220, counter++= 10, counter incremented) thread(5220, counter++= 11, counter incremented) thread(5220, counter++= 12, counter incremented) thread(5220, counter++= 13, counter incremented) thread(5220, counter++= 14, counter incremented) thread(5220, counter++= 15, counter incremented) thread(5220, counter++= 16, counter incremented) thread(5220, counter++= 17, counter incremented) thread(5220, counter++= 18, counter incremented) thread(5220, counter++= 19, counter incremented) thread(5220, counter++= 20, counter incremented) thread(5220, counter+2=2, counter incremented thread(5220, counter+2=4, counter incremented thread(5220, counter+2=6, counter incremented thread(5220, counter+2=8, counter incremented thread(5220, counter+2=10, counter incremented thread(5220, counter+2=12, counter incremented thread(5220, counter+2=14, counter incremented thread(5220, counter+2=16, counter incremented thread(5220, counter+2=18, counter incremented thread(5220, counter+2=20, counter incremented thread(5220, counter+2=22, counter incremented thread(5220, counter+2=24, counter incremented thread(5220, counter+2=26, counter incremented thread(5220, counter+2=28, counter incremented thread(5220, counter+2=30, counter incremented thread(5220, counter+2=32, counter incremented thread(5220, counter+2=34, counter incremented thread(5220, counter+2=36, counter incremented thread(5220, counter+2=38, counter incremented thread(5220, counter+2=40, counter incremented
上の例では、CThreadクラスからCIncrementThreadクラスを派生しています。クラス定義で、仮想メンバ関数OnTask()とOnTask(LPVOID)を再定義しています。OnTask()の実装では、オブジェクトのカウンタ変数に1を加えます。もう一方のOnTask(LPVOID)では、整数値へのポインタを引数で受け取り、そのポインタの値をカウンタのメンバ変数に加えます。この例は、スレッドが処理できる2種類のイベントに対応しています。カウンタ変数は複数のスレッドからアクセスされる可能性があるので、CThread::m_mutexオブジェクトを使用して、単一のスレッドからのみアクセスされるようにしています。
均一型非同期スレッド処理(HAT:Homogeneous Asynchronous Threading)のスレッドの実装では、CThreadクラスとCTaskクラスの両方を使用します。
#include "Thread.h" class CTaskIncrementer: public CTask { private: int counter; int incr; public: void SetIncr(int iValue) { m_mutex.Lock(); incr = iValue; m_mutex.Unlock(); } int GetIncrementValue() { int incrValue; m_mutex.Lock(); incrValue=incr; m_mutex.Unlock(); return incrValue; } int GetValue() { int counterValue = 0; m_mutex.Lock(); // protect the counter variable counterValue = counter; m_mutex.Unlock(); return counter; } BOOL Task() { ThreadId_t id; Thread(&id); m_mutex.Lock(); printf("\tthread(%ld, counter+%d=%d, counter incremented\n", id,incr,(counter+=incr)); m_mutex.Unlock(); return TRUE; } CTaskIncrementer(){counter=0;} ~CTaskIncrementer(){} }; int main(int argc, char *argv[]) { CTaskIncrementer incr; CThread thr; incr.SetIncr(2); while( incr.GetValue() < 40 ) thr.Event(&incr); }
thread(5700, counter+2=2, counter incremented thread(5700, counter+2=4, counter incremented thread(5700, counter+2=6, counter incremented thread(5700, counter+2=8, counter incremented thread(5700, counter+2=10, counter incremented thread(5700, counter+2=12, counter incremented thread(5700, counter+2=14, counter incremented thread(5700, counter+2=16, counter incremented thread(5700, counter+2=18, counter incremented thread(5700, counter+2=20, counter incremented thread(5700, counter+2=22, counter incremented thread(5700, counter+2=24, counter incremented thread(5700, counter+2=26, counter incremented thread(5700, counter+2=28, counter incremented thread(5700, counter+2=30, counter incremented thread(5700, counter+2=32, counter incremented thread(5700, counter+2=34, counter incremented thread(5700, counter+2=36, counter incremented thread(5700, counter+2=38, counter incremented thread(5700, counter+2=40, counter incremented
インターバル(間隔)ドリブンのスレッドは、定められた間隔ごとに起き、環境が変化しているかを確認して、その変化に対応します。そして、次の間隔までスリープしてから、同じことを繰り返す、というスレッドです。インターバルドリブンのスレッドを実装するには、CThreadクラスを派生して、OnTask(LPVOID)を再定義します。スレッドのインスタンスを作成したら、SetThreadTypeメンバ関数を呼び出し、そのパラメータとして、ThreadTypeIntervalDrivenと、ミリ秒単位の間隔を指定します。
#include "Thread.h" class CIncrementThread : public CThread { public: int counter; virtual BOOL OnTask() { ThreadId_t id; GetId(&id); //don't use cout here, output could be broken up due to //threading m_mutex.Lock(); // protect the counter variable printf("\tthread(%ld, counter++= %d, counter incremented)\n", id,(++counter)); m_mutex.Unlock(); return TRUE; } int GetValue() { int counterValue = 0; m_mutex.Lock(); // protect the counter variable counterValue = counter; m_mutex.Unlock(); return counter; } void Reset() { m_mutex.Lock(); counter = 0; m_mutex.Unlock(); } CIncrementThread(){counter=0;} ~CIncrementThread(){} }; int main( int argc, char *argv[] ) { CIncrementThread thr; thr->SetThreadType(ThreadTypeIntervalDriven,100); Sleep(500); }
thread(6104, counter++= 12, counter incremented) thread(6104, counter++= 13, counter incremented) thread(6104, counter++= 14, counter incremented) thread(6104, counter++= 15, counter incremented) thread(6104, counter++= 16, counter incremented)
まとめ
以上で、スレッドオブジェクトは完成です。Linuxでテストしてみたところ、各クラスはきちんと動作しました。SunOSや他のUNIXプラットフォームにも対応しているはずですが、そちらはまだテストしていません。Windowsでコンパイルするときには、コード生成時に/Mtまたは/Mtdを必ず指定してください。このアプリケーションはマルチスレッドアプリケーションであるという意味の指定です。Linuxの場合は、次のようなメイクファイルでOKです。
CC=g++ LIBS=-lpthread -lrt CFLAGS=-DSUNOS -DNANO_SECOND_SLEEP OBJS=Thread.cpp EventClass.cpp MutexClass.cpp main.cpp EXECS = thread all: $(EXECS) thread: $(OBJS) $(CC) $(CFLAGS) -o thread $(OBJS) $(LIBS) clean:; rm -f *.o $(EXECS)