マルチスレッド: 同期クラスの使用法
リソースにアクセスする複数のスレッドの同期は、マルチスレッド アプリケーションを書くときに共通の問題です。 複数のスレッドから同じデータに同時にアクセスすると、予測できない結果になることがあります。 たとえば、あるスレッドで構造体の内容を更新しているときに、別のスレッドが同じ構造体の内容を読み取るということが考えられます。 この場合、後者のスレッドが実際に読み取った内容が、古いデータになるか更新後のデータになるかはわかりません。古いデータと更新データが混在することも考えられます。 MFC (Microsoft Foundation Class) の同期クラスと同期アクセス クラスを使うと、この問題を解決できます。 このトピックでは、これらのクラスを使用して、一般的なマルチスレッド アプリケーションでスレッド セーフなクラスを作成する方法を説明します。
一般的なマルチスレッド アプリケーションには、スレッド間の共有リソースを表すクラスがあります。 スレッドセーフなクラスをデザインすると、同期関数を呼び出す必要がありません。 リソースへのアクセスをすべてクラス内部で処理するので、プログラマはクラスの破損などを気にせずに、クラスを最大限に活用する方法だけを考えることができます。 スレッド セーフなクラスを作成するには、同期クラスをリソース クラス内にマージすることが効果的な方法です。 同期クラスの共有クラスへのマージは簡単に行うことができます。
たとえば、アカウントのリンク リストを保持するアプリケーションの場合を考えます。 このアプリケーションでは 3 つまでのアカウントを個別のウィンドウで調べることができます。ただし、一度に更新できるアカウントは 1 つだけとします。 更新されたデータはネットワークを通じてデータ アーカイブに送られます。
このアプリケーションでは、3 種類の同期クラスをすべて使います。 同時に調べることができるアカウントは 3 つまでなので、CSemaphore を使って 3 つのビュー オブジェクトだけにアクセスを制限します。 4 番目のアカウントを表示しようとすると、アプリケーションは最初の 3 つのウィンドウのいずれかが閉じるのを待つか、失敗します。 アカウントを更新する場合、アプリケーションは CCriticalSection を使って一度に 1 つのアカウントだけを更新します。 更新に成功すると、CEvent を発行し、このイベントの発行を待っていたスレッドを解放します。 新しいデータは、このスレッドからデータ アーカイブに送られます。
スレッドセーフなクラスのデザイン
スレッドセーフなクラスにするには、まず、該当する同期クラスをデータ メンバーとして共有クラスに追加します。 上のアカウント管理の例では、CSemaphore データ メンバーをビュー クラスに追加し、CCriticalSection データ メンバーをリンク リスト クラスに追加し、CEvent データ メンバーをデータ ストレージ クラスに追加します。
次に、クラス内のデータを変更する、または被制御リソースにアクセスするすべてのメンバー関数への同期呼び出しを追加します。 それぞれの関数では、CSingleLock オブジェクトまたは CMultiLock オブジェクトを作成し、作成したオブジェクトの Lock 関数を呼び出す必要があります。 ロック オブジェクトがスコープ外に出て破棄されると、このオブジェクトのデストラクターによって Unlock が呼び出され、リソースが解放されます。 必要に応じて、Unlock を直接呼び出すこともできます。
このようにスレッド セーフなクラスをデザインすると、非スレッド セーフなクラスと同じように、マルチスレッド アプリケーションで簡単に使用できるだけでなく、安全性も保証されます。 同期オブジェクトと同期アクセス オブジェクトをリソースのクラスにカプセル化すると、同期コードを使わずに、スレッドを安全に使用できます。
次のコード例では、共有リソース クラスで宣言されたデータ メンバー m_CritSection (CCriticalSection 型) と CSingleLock オブジェクトを使って、この方法を示します。 CWinThread から派生する共有リソースの同期は、m_CritSection オブジェクトのアドレスを使って CSingleLock オブジェクトを作成することによって行います。 まず、リソースのロックを行い、ロックを取得すると、共有オブジェクト側の作業は完了です。 作業が完了すると、Unlock 呼び出しによってリソースのロックが解除されます。
CSingleLock singleLock(&m_CritSection);
singleLock.Lock();
// resource locked
//.usage of shared resource...
singleLock.Unlock();
注意
CCriticalSection には、その他の MFC 同期クラスと異なり、期限付きのロック要求オプションがありません。 スレッドが解放されるまでの待機時間は無限です。
この手法の欠点は、同期オブジェクトを追加しない場合と比べて、クラスの動作が少し遅くなることです。 また、同期オブジェクトを複数のスレッドで削除した場合は、マージが成功するとは限りません。 このような場合は、同期オブジェクトを別個に管理します。
状況に応じて同期クラスを使い分ける方法については、「マルチスレッド : 同期クラスの使い分け」を参照してください。 同期の詳細については、Windows SDK の「Synchronization」を参照してください。 MFC によるマルチスレッドのサポートの詳細については、「C++ と MFC を使用するマルチスレッド」を参照してください。