ホームに戻る
 14、スレッドと同期

スレッドは特に何もしなければ非同期である。
各々のスレッドが実行される順番の保証は無い。
よって複数のスレッドが同一のファイルに書き込みを行った場合に
結果は予想できないものとなり、このようなプログラムは許されない。

例えばスレッド間の同期を考える場合、
グローバル変数のフラグを用いる方法を考える。
例えばあるスレッドがファイルへの書き込みを行う場合、
書き込み前にフラグを立てて、
次のスレッドが書き込みを行う場合にはこのフラグを参照して、
もしフラグが立っていれば処理を待つという方法が考えられる。
この方法は一見良いように見えるが、
例えばフラグを確認しフラグを立てようとしたその時に
スレッドが切り替わった場合などに対応できない。
また処理を待つループを作るのもCPU資源の無駄となる。
よってスレッドの同期は言語のレベルでは不可能であり、
言語での制御を諦め、APIを利用することを考える。

 クリティカルセクション

スレッド同期のいちばん簡単な方法はクリティカルセクションを使うこと。
特徴は同一プロセス内でしか有効でないこと。
クリティカルセクションはまず初期化部分を作成し、
危険領域に入る部分と出る部分を指定する。
この危険領域には1つのスレッドしか入れなくなり、
次のスレッドは先のスレッドが領域を出るまで待機する。
セクションは使用しなくなったら片付けをする必要がある。

CRITICAL_SECTION section;

void thread(){
  EnterCriticalSection(&section);
  //危険な処理
  LeaveCriticalSection(&section);
}

void main(){
  InitializeCriticalSection(&section);
  //スレッド
  DeleteCriticalSection(&section);
}

TryEnterCriticalSection はクリティカルセクションの状況を調べる。
調べるだけなので処理はすぐに返る。
このAPIはクリテカルセクションが空かセクション内である場合 0 を返す。
クリテカルセクションを別スレッドが保持している場合は 0 以外の数値。

 ミューテックス

ミューテックスは判別用の文字列を使用することで
クリティカルセクションが同一プロセス内でしか有効でないのに対し
複数プロセス間での排他処理を実現する。

例えばあるプロセスである文字列を目印にミューテックスを確保する場合、
CreateMutex を呼び出す。
第1引数はセキュリティ属性、第2引数はすぐに所有権を得るかどうか?
すぐにミューテックスを確保する場合は TRUE とする。
第3引数は目印となる文字列を指定する。
もし自プロセスがミューテックスを作成したならそのハンドルを返し、
もし他のプロセスが既に作成していたとしてもそのハンドルを返す。
作成されたか既にあったかは GetLastError で 0 が返れば作成され、
ERROR_ALREADY_EXISTS であれば既にあったことになる。
危険領域前の待機は WaitForSingleObject で行う。
危険領域を出たら ReleaseMutex とする。
ミューテックスのハンドルは各プロセスで閉じる必要がある。
ただし、他のプロセスが使用している場合は実際に閉じられているわけではない。
使用していた最後のプロセスがハンドルを閉じたときに
システムの判断によってミューテックスのハンドルが閉じられる。

HANDLE hMutex;

void thread(){
  WaitForSingleObject(hMutex, INFINITE);
  //危険な処理
  ReleaseMutex(hMutex);
}

void main(){
  hMutex = CreateMutex(NULL, FALSE, "mutex_string");
  //スレッド
  CloseHandle(hMutex);
}

 セマフォ

セマフォはミューテックスへの機能の付け足しと考えて良い。
複数のスレッドのうち例えば3つまで許可するなど、
数の制限をしたいときに利用される。
主に負荷軽減のための制限などに用いられることが多い。
使い方はミューテックスと同じように考えて良い。
CreateSemaphore などのAPIを用いることになる。

 イベント

イベントは動作はミューテックスと同じだが、
ミューテックスが他の処理が終わるのを待つだけなのに対して、
イベントは他のスレッドから処理を強制的に開始させることができる。
イベントはシグナルがオンのときに開始されオフのとき
WaitForSingleObject の位置で待機する。
強制的にイベントを操作する場合は SetEvent が PulseEvent を用いる。
それぞれの方法には手動リセットと自動リセットの方法があり、
組み合わせによって4つのやり方がある。

SetEvent(手動):ResetEvnt が呼ばれるまでシグナルがオンを維持。
SetEvent(自動):待機中のスレッドを1つ動かすまでシグナルをオン、その後シグナルをオフ。
PulseEvent(手動):待機中のスレッドをすべて動かし自動でシグナルをオフ。
PulseEvent(自動):待機中のスレッドがあれば1つ動かし、無くてもすぐにシグナルをオフ。

SetEvent、PulseEvent、ResetEvnt は第1引数にイベントのハンドルを指定。

HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes, // セキュリティ記述子
  BOOL bManualReset,                       // リセットのタイプ
  BOOL bInitialState,                      // 初期状態
  LPCTSTR lpName                           // イベントオブジェクトの名前
);

lpEventAttributes:セキュリティ属性
bManualReset:TRUE で手動リセット。FALSE で自動リセット。
bInitialState:TRUE でシグナルがオン。FALSE でシグナルがオフ。
lpName:イベントを識別する文字列。

戻り値は成功でイベントハンドルが返り、失敗で NULL が返る。

inserted by FC2 system