共有オブジェクトに対処する方法とマルチスレッド化は iostream オブジェクトをスレッドの局所的なオブジェクトにして、問題そのものを解消してしまうことです。たとえば、次を見てください。
スレッドのエントリ関数の中でオブジェクトを局所的に宣言する。
スレッド固有データの中でオブジェクトを宣言する。(スレッド固有データの使用法については、thr_keycreate(3T) のマニュアルページを参照してください。
ストリームオブジェクトを特定のスレッド専用にする。このオブジェクトスレッドは、慣例により非公開 (private) になります。
ただし、デフォルトの共有標準ストリームオブジェクトを初めとして、多くの場合はオブジェクトをスレッドの局所的なオブジェクトにすることはできません。そのため、別の手段が必要です。
iostream クラスのオブジェクトに対する一続きの操作を不可分的に実行するには、何らかのロック処理が必要です。ただし、ロック処理を行うと、シングルスレッドアプリケーションの場合でさえも、オーバーヘッドが多少増加します。ロック処理を追加する必要があるか、それとも iostream オブジェクトをスレッドの非公開オブジェクトにすればよいかは、アプリケーションで採用しているスレッドモデル (独立スレッドと連携スレッドのどちらを使用しているか) によって決まります。
スレッドごとに別々の iostream オブジェクトを使用してデータを入出力する場合は、それぞれの iostream オブジェクトが、該当するスレッドの非公開オブジェクトになります。ロック処理の必要はありません。
複数のスレッドを連携させる (これらのスレッドの間で、同じ iostream オブジェクトを共有させる) 場合は、その共有オブジェクトへのアクセスの同期をとる必要があり、何らかのロック処理によって、一続きの操作を不可分的にする必要があります。
iostream ライブラリには、iostream オブジェクトに対する一続きの操作をロックするための stream_locker クラスが含まれています。これにより、iostream オブジェクトのロックを動的に切り換えることで生じるオーバーヘッドを最小限にできます。
stream_locker クラスのオブジェクトを使用すると、ストリームオブジェクトに対する一続きの操作を不可分的にできます。たとえば、次の例を考えてみましょう。このコードは、ファイル内の位置を特定の場所まで移動し、その後続のデータブロックを読み込みます。
#include <fstream.h> #include <rlocks.h> void lock_example (fstream& fs) { const int len = 128; char buf[len]; int offset = 48; stream_locker s_lock(fs, stream_locker::lock_now); .....// open file fs.seekg(offset, ios::beg); fs.read(buf, len); } |
この例では、stream_locker オブジェクトのコンストラクタが実行されてから、デストラクタが実行されるまでが、一度に 1 つのスレッドしか実行できない相互排他領域になります。デストラクタは、lock_example 関数が終了したときに呼び出されます。この stream_locker オブジェクトにより、ファイル内の特定のオフセットへの移動と、ファイルからの読み込みの連続的な (不可分的な) 実行が保証され、ファイルからの読み込みを行う前に、別のスレッドによってオフセットが変更されてしまう可能性がなくなります。
stream_locker オブジェクトを使用して、相互排他領域を明示的に定義することもできます。次の例では、入出力操作と、そのあとで行うエラーチェックを不可分的にするために、stream_locker オブジェクトのメンバー関数、lock と unlock を呼び出しています。
{ ... stream_locker file_lck(openfile_stream, stream_locker::lock_defer); .... file_lck.lock(); // lock openfile_stream openfile_stream << "Value: " << int_value << "\n"; if(!openfile_stream) { file_error("Output of value failed\n"); return; } file_lck.unlock(); // unlock openfile_stream } |