マルチスレッド関連の実装方法に迷いが生じたので、ちょっと考えを整理する。
■やりたいことは何か
やりたいことはスレッドからスレッドへの通知?いや違う。それは手段であって、目的ではない。やりたいのは、
- 処理Aが終了したあと、その結果を使う処理Bを起動する
- 処理Aが終了したあと、次の同様の処理A'を起動する
- 処理A'、処理Bはリソースが空くまで動作しない
である。つまり条件付きで処理を開始したい、ということになる。
これを実装するシンプルな方法は、こんな感じだろうか。
ThreadA(){ WaitForResource(); Do(); BeginThread(ThreadA); BeginThread(ThreadB);}
ThreadB(){ WaitForResource(); Do();}
この方式の問題点は、ThreadBの速度がThreadAよりも遅い場合である。リソースの空きを待つThreadBがどんどん増えてしまう。最悪、WindowsのHANDLE上限を超えるかもしれない。となると、ThreadBの実行状況に応じてThreadAの実行を制御する必要がある。ThreadAの実行制御はWaitForResource()が担っているので、これがThreadBの状況も監視するようになっていれば良さそうだ。
では、WaitForResource()はどのようなものなのだろうか?要件は以下だろう。
- 実行に必要なリソースの空きができるまで実行を停止する
- 後続処理の処理状況に空きができるまで実行を停止する
- 異なる複数のスレッドからの呼び出しに対応する
これはCSemaphore(セマフォ)で実現できそうだなあ。
■書き込みと読み込みの同時実行防止
書き込みと読み込みを同時に実行すると、遅くなる気がする。なので排他処理にしたい。そして、できれば書き込みを優先したい。これをセマフォでやるには、どうすればいいのだろう?まずは、優先を考えずに擬似コードを書いてみる。
CSemaphore hdd(initial=0,max=1);
CSemaphore CPU(initial=0,max=4);
ReadThread(){ Lock(hdd); ReadFile(); UnLock(hdd); BeginThread(CPUThread); BeginThread(ReadThread);}
CPUThread(){ Lock(CPU); Calc(); UnLock(CPU); Lock(hdd); WriteFile(); UnLock(hdd);}
このコードはデッドロックは起きなさそうだがWriteFile待ちのCPUThreadが大量にできそうだ。あまりよくない。
ならば、ReadThreadの開始に制限を追加したこれならどうか?
ReadThread(){ Lock(CPU); Lock(hdd); ReadFile(); UnLock(hdd); BeginThread(ReadThread); Calc(); Lock(hdd); WriteFile();UnLock(hdd); UnLock(CPU);}
なお、CPUの数だけReadThreadができうるので、CPUThreadはReadThreadと統合してみた。すっきりはしたが、CPUの空きができるまでファイルを読み込まないというのは悲しい。となると、Lock(CPU)の位置を調整すればいいのか?ファイルを読み込んでからCPU実行待ち状態にしよう。次のファイル読み込みスレッドも、CPUリソースが空くまで待つか。
ReadThread(){ Lock(hdd); ReadFile(); UnLock(hdd); Lock(CPU); BeginThread(ReadThread); Calc(); Lock(hdd); WriteFile();UnLock(hdd); UnLock(CPU);}
だいぶ良くなった。だがしかし、複数のスレッドがたまたま同時に終了した場合、こんどはReadFileが間に合わなくなりCPUが暇してしまう。できればCPUコア数分ぐらいは先読みしておきたい。ということは、読み込みスレッドのBeginThread実行条件とCPU計算実行条件を分ける必要があるのか。こんな感じ?
CSemaphore ReadBuff(initial=0, max=4+4);
ReadThread(){ Lock(hdd); ReadFile(); UnLock(hdd); Lock(ReadBuff); BeginThread(ReadThread); Lock(CPU); Calc(); Lock(hdd); WriteFile();UnLock(hdd); UnLock(CPU); UnLock(ReadBuff); }
UnLock(ReadBuff)の位置はもっと前でもデッドロックせずに動作するが、Lock(hdd);より後ろとすることでWriteFileがReadFileよりも先に実行されるはず。うん、これでOKかな。あとは実装だ。