はじめに
先日、私の兄弟から、オブジェクト指向のスレッド処理を楽にするC++クラスを簡単に作成する方法はないだろうかという相談を受けました。私はこれまで、さまざまなマルチスレッドライブラリを作成した経験がありますが、いずれもC言語によるものでした。低レベルのプログラミングには私はいつもC言語を使用し、C++はもっぱらGUI開発に使用しています。CodeGuruにはオブジェクト指向でスレッド処理を行うクラスの優れた例が色々と掲載されていますが、彼の要望をすべて満たし、かつ私の好奇心をも満たすクラスは見当たりませんでした。彼が求めていたのは、次のような特徴を備えたスレッドクラスでした。
- イベントドリブンとインターバル(間隔)ベースの両方の非同期スレッド処理に対応している
- 均一型と細分型の両方のスレッド処理に対応している
- 複数タスクを格納して処理できる、先着順(FCFS:First Come First Serve)方式のスタックキューを備えている
- 移植可能である
- 簡単に実装できる
そこで私は、CThreadという新しいクラスと、併せて使用するサポートクラスをいくつか作成しました。サポートクラスとして用意したのは、CMutexClassクラス、CEventClassクラス、CTaskクラスです。CMutexClassとCEventClassは、リソース管理を行うためのクラスです。CTaskは、均一型の非同期スレッド処理を行うクラスを作成するための基本クラスです。
スレッド処理とは何か
プロセスは必ず1つ以上のスレッドを制御しており、各プロセスは一度に少なくとも1つのタスクを実行できます。複数のスレッドを制御しているプロセスのことを、マルチスレッドプロセスといいます。マルチスレッドプロセスでは、同じプロセスの環境内から複数のタスクを非同期で実行できます。
リソース管理――スレッドの同期
同じマルチスレッドプロセスに属する各スレッドは、同じリソースを共有します。したがって、データ整合性を確保するために、OSレベルの制御機構が必要です。データ整合性が損なわれるのは、たとえばあるスレッドが変数を変更しようとしているときに別のスレッドがその変数を読み込もうとした場合や、2つのスレッドが同じ変数を同時に変更しようとした場合です。OSには、こうした状況を防ぐためのしくみが用意されています。その名は、相互排他オブジェクト(Mutual Exclusion Object)、略して「ミューテックス(mutex)」です。マルチスレッドアプリケーションでは、プログラムで配置したミューテックスによって、複数のスレッドが単一のリソースに同時にアクセスする事態を防ぎます。
スレッドは、リソースにアクセスする必要が生じると、まずミューテックスを取得します。いずれかのスレッドがミューテックスを取得しているときには、同じミューテックスを取得しようとした他のスレッドはブロックされ、CPU使用率の低い待ち状態に置かれます。データアクセスが完了したスレッドは、対応するミューテックスを解放します。これを受けて、他のスレッドがそのミューテックスを取得できるようになり、対応するデータにアクセスできます。
ミューテックスの実装に問題があると、いわゆる「デッドロック」が発生することがあります。デッドロックとは、1つまたは複数のスレッドが、同じリソースへのアクセスを求めて競合している状態です。デッドロックは、スレッドがミューテックスを2回取得しようとした場合にも発生することがあります。
スレッドA | スレッドB |
ミューテックス(1)を取得し、データ項目1を変更 | ミューテックス(2)を取得し、データ項目2を変更 |
データ項目2を参照するためにミューテックス(2)が必要 | データ項目1を参照するためにミューテックス(1)が必要 |
上の例ではデッドロックが発生します。スレッドAは、スレッドBが保持するミューテックス(2)を取得しようとしてブロック状態になり、スレッドBは、スレッドAが保持するミューテックス(1)を取得しようとしてブロック状態になるからです。
UNIXの条件変数も、ミューテックスと同様、スレッドを同期させるためのしくみの1つです。条件変数では、スレッド同士を連係させることが可能です。あるスレッドから別のスレッドに対し、変更が発生したことを通知できます。Windowsでは、その機能はイベントで実現されます。
オペレーティングシステムコール
次の表は、CMutexClass、CEventClass、CTask、CThreadの各クラスでスレッド処理の実装に使用した関数の一覧です。
関数 | OS | 説明 | 使用したクラス |
CreateThread | Windows | Windowsでスレッドを作成する | CThread |
pthread_create | UNIX - POSIXスレッド | UNIXでスレッドを作成する | CThread |
pthread_join | UNIX - POSIXスレッド | UNIXでスレッドの終了を待機する | CThread |
pthread_attr_init | UNIX - POSIXスレッド | スレッド属性構造体をデフォルト値に設定する | CThread |
pthread_attr_setstacksize | UNIX - POSIXスレッド | スレッド属性構造体のスタックサイズ値を設定する | CThread |
WaitForSingleObject | Windows | オブジェクトがシグナル状態になるのを待機する | CThread、CMutexClass、CEventClass |
CreateMutex | Windows | 名前付きまたは匿名のミューテックスを作成する | CMutexClass |
CloseHandle | Windows | Windowsハンドルへのリソース割り当てを解放する | CMutexClass、CEventClass、CThread |
ReleaseMutex | Windows | WaitForSingleObjectでロックされた取得済みのミューテックスを解放する | CMutexClass、CEventClass |
pthread_mutexattr_init | UNIX - POSIXスレッド | ミューテックス属性構造体を初期化する | CMutexClass、CEventClass |
pthread_mutex_init | UNIX - POSIXスレッド | 指定の属性構造体でミューテックスを初期化する | CMutexClass、CEventClass |
pthread_mutex_lock | UNIX - POSIXスレッド | ミューテックスをロックする | CMutexClass、CEventClass |
pthread_mutex_unlock | UNIX - POSIXスレッド | pthread_mutex_lockでロックしたミューテックスのロックを解除する | CMutexClass、CEventClass |
pthread_mutex_destroy | UNIX - POSIXスレッド | ミューテックスに割り当てられたリソースを解放する | CMutexClass、CEventClass |
CreateEvent | Windows | Windowsのイベントオブジェクトを作成する | CEventClass |
SetEvent | Windows | Windowsのイベントオブジェクトをシグナル状態に設定する | CEventClass |
pthread_cond_signal | UNIX - POSIXスレッド | pthread_cond_waitでブロックされたスレッドのブロックを解除する | CEventClass |
pthread_cond_wait | UNIX - POSIXスレッド | 条件変数に基づいてブロックする | CEventClass |
pthread_cond_init | UNIX - POSIXスレッド | 条件変数を初期化する | CEventClass |