Sun Studio 12 Update 1: C++ ユーザーズガイド

11.4.1 マルチスレッドで使用しても安全な iostream ライブラリの構成

マルチスレッドで使用しても安全な iostream ライブラリの構成は、従来の iostream ライブラリの構成と多少異なります。マルチスレッドで使用しても安全な iostream ライブラリのインタフェースは、iostream クラスやその基底クラスの公開および限定公開のメンバー関数を示していて、従来のライブラリと整合性が保たれていますが、クラス階層に違いがあります。詳細については、「11.4.2 iostream ライブラリのインタフェースの変更」を参照してください。

従来の中核クラスの名前が変更されています (先頭に unsafe_ という文字列が付きました)。iostream パッケージの中核クラスを表 11–1 に示します。

表 11–1 iostream の中核クラス

クラス 

内容の説明 

stream_MT

マルチスレッドで使用しても安全なクラスの基底クラス 

streambuf

バッファーの基底クラス 

unsafe_ios

各種のストリームクラスに共通の状態変数 (エラー状態、書式状態など) を収容するクラス 

unsafe_istream

streambuf から取り出した文字の並びを、書式付き/書式なし変換する機能を持つクラス

unsafe_ostream

streambuf に格納する文字の並びを、書式付き/書式なし変換する機能を持つクラス

unsafe_iostream

unsafe_istream クラスと unsafe_ostream クラスを組み合わせた入出力兼用のクラス

マルチスレッドで使用しても安全なクラスは、すべて基底クラス stream_MT の派生クラスです。また、これらのクラスは、streambuf を除いて、(先頭に unsafe_ が付いた) 従来の基底クラスの派生クラスでもあります。この例を次に示します。


class streambuf: public stream_MT {...};
class ios: virtual public unsafe_ios, public stream_MT {...};
class istream: virtual public ios, public unsafe_istream {...};

stream_MT には、それぞれの iostream クラスをマルチスレッドで使用しても安全にするための相互排他 (mutex) ロック機能が含まれています。また、このクラスには、マルチスレッドで使用しても安全な属性を動的に変更できるように、ロックを動的に有効および無効にする機能もあります。入出力変換とバッファー管理の基本機能は、従来の unsafe_ クラスにまとめられています。したがって、ライブラリに新しく追加されたマルチスレッドで使用しても安全な機能は、その派生クラスだけで使用できます。マルチスレッドで使用しても安全なクラスには、従来の unsafe_ 基底クラスと同じ公開メンバー関数と限定公開メンバー関数が含まれています。これらのメンバー関数は、オブジェクトをロックし、unsafe_ 基底クラスの同名の関数を呼び出し、そのあとでオブジェクトのロックを解除するラッパーとして働きます。


注 –

streambuf クラスは、unsafe クラスの派生クラスではありません。streambuf クラスの公開メンバー関数と限定公開メンバー関数は、ロックを行うことで再入可能になります。ロックを行わない関数も用意されています。これらの関数は、名前の後ろに _unlocked という文字列が付きます。


11.4.1.1 公開変換ルーチン

iostream のインタフェースには、マルチスレッドで使用しても安全な、再入可能な公開関数が追加されています。これらの関数は、追加引数としてユーザーが指定したバッファーを受け取ります。これらの関数を次に示します。

表 11–2 マルチスレッドで使用しても安全な、再入可能な公開関数

関数 

内容の説明  

char *oct_r (char *buf,

int buflen,

long num,

int width)

数値を 8 進数の形式で表現した ASCII 文字列のポインタを返す。width が 0 (ゼロ) ではない場合は、その値が書式設定用のフィールド幅になります。戻り値は、ユーザーが用意したバッファーの先頭を指すとはかぎりません。 

char *hex_r (char *buf,

int buflen,

long num,

int width)

数値を 16 進数の形式で表現した ASCII 文字列のポインタを返す。width が 0 (ゼロ) ではない場合は、その値が書式設定用のフィールド幅になります。戻り値は、ユーザーが用意したバッファーの先頭を指すとはかぎりません。 

char *dec_r (char *buf,

int buflen,

long num,

int width)

数値を 10 進数の形式で表現した ASCII 文字列のポインタを返す。width が 0 (ゼロ) ではない場合は、その値が書式設定用のフィールド幅になります。戻り値は、ユーザーが用意したバッファーの先頭を指すとはかぎりません。 

char *chr_r (char *buf,

int buflen,

long num,

int width)

文字 chr を含む ASCII 文字列のポインタを返す。width が 0 (ゼロ) ではない場合は、その値と同じ数の空白に続けて chr が格納されます。戻り値は、ユーザーが用意したバッファーの先頭を指すとはかぎりません。

char *form_r (char *buf,

int buflen,

long num,

int width)

sprintf によって書式設定した文字列のポインタを返す。書式文字列 format 以降のすべての引数を使用します。ユーザーが用意したバッファーに、変換後の文字列を収容できるだけの大きさがなければいけません。


注 –

以前の libC との互換性を確保するために提供されている iostream ライブラリの公開変換ルーチン (octhexdecchrform) は、マルチスレッドで使用すると安全ではありません。


11.4.1.2 マルチスレッドで使用しても安全な libC ライブラリを使用したコンパイルとリンク

libC ライブラリの iostream クラスを使用した、マルチスレッド環境用のアプリケーションを構築するには、-mt オプションを付けてソースコードのコンパイルとリンクを行う必要があります。このオプションを付けると、プリプロセッサに -D_REENTRANT が渡され、リンカーに -lthread が渡されます。


注 –

libClibthread へのリンクを行うには、(-lthread オプションではなく) -mt オプションを使用します。このオプションを使用しないと、ライブラリが正しい順番でリンクされないことがあります。誤って -lthread オプションを使用すると、作成したアプリケーションが正しく機能しない場合があります。


iostream クラスを使用するシングルスレッドアプリケーションについては、コンパイラオプションやリンカーオプションを特に必要としません。オプションを何も指定しなかった場合は、コンパイラは libC ライブラリへのリンクを行います。

11.4.1.3 マルチスレッドで使用しても安全な iostream の制約

iostream ライブラリのマルチスレッドでの安全性には制約があります。これは、マルチスレッド環境で iostream オブジェクトが共有された場合に、iostream を使用するプログラミング手法の多くが安全ではなくなるためです。

エラー状態のチェック

マルチスレッドでの安全性を実現するには、エラーの原因になる入出力操作を含んでいる危険領域で、エラーチェックを行う必要があります。エラーが発生したかどうかを確認するには次のようにします。


例 11–1 エラー状態のチェック


#include <iostream.h>
enum iostate {IOok, IOeof, IOfail};

iostate read_number(istream& istr, int& num)
{
    stream_locker sl(istr, stream_locker::lock_now);

    istr >> num;

    if (istr.eof()) return IOeof;
    if (istr.fail()) return IOfail;
    return IOok;
}

この例では、stream_locker オブジェクト sl のコンストラクタによって、istream オブジェクト istr がロックされます。このロックは、read_number が終了したときに呼び出される sl のデストラクタによって解除されます istr

最後の書式なし入力操作で抽出された文字列の取得

マルチスレッドでの安全性を実現するには、最後の入力操作と gcount の呼び出しを行う期間に、istream オブジェクトを排他的に使用するスレッドの内部から、gcount 関数を呼び出す必要があります。gcount は次のように呼び出します。


例 11–2 gcount の呼び出し


#include <iostream.h>
#include <rlocks.h>
void fetch_line(istream& istr, char* line, int& linecount)
{
    stream_locker sl(istr, stream_locker::lock_defer);

    sl.lock(); // lock the stream istr
    istr >> line;
    linecount = istr.gcount();
    sl.unlock(); // unlock istr
    ...
}

この例では、stream_locker クラスの lock メンバー関数を呼び出してから unlock メンバー関数を呼び出すまでが、プログラムの相互排他領域になります。

ユーザー定義の入出力操作

マルチスレッドでの安全性を実現するには、別々の操作を特定の順番で行う必要があるユーザー定義型用の入出力操作を、危険領域としてロックする必要があります。この入出力操作の例を次に示します。


例 11–3 ユーザー定義の入出力操作


#include <rlocks.h>
#include <iostream.h>
class mystream: public istream {

    // other definitions...
    int getRecord(char* name, int& id, float& gpa);
};

int mystream::getRecord(char* name, int& id, float& gpa)
{
    stream_locker sl(this, stream_locker::lock_now);

    *this >> name;
    *this >> id;
    *this >> gpa;

    return this->fail() == 0;
}

11.4.1.4 マルチスレッドで使用しても安全なクラスのパフォーマンスオーバーヘッドの削減

現行の libC ライブラリに含まれているマルチスレッドで使用しても安全なクラスを使用すると、シングルスレッドアプリケーションの場合でさえも多少のオーバーヘッドが発生します。libCunsafe_ クラスを使用すると、このオーバーヘッドを回避できます。

次のようにスコープ決定演算子を使用すると、unsafe_ 基底クラスのメンバー関数を実行できます。


    cout.unsafe_ostream::put(’4’);

    cin.unsafe_istream::read(buf, len);

注 –

unsafe_ クラスは、マルチスレッドアプリケーションでは安全に使用できません。


unsafe_ クラスを使用する代わりに、cout オブジェクトと cin オブジェクトを unsafe にしてから、通常の操作を行うこともできます。ただし、パフォーマンスが若干低下します。unsafecoutcin は、次のように使用します。


例 11–4 マルチスレッドでの安全性の無効化


#include <iostream.h>
//disable mt-safety
cout.set_safe_flag(stream_MT::unsafe_object);    
//disable mt-safety
cin.set_safe_flag(stream_MT::unsafe_object);    
cout.put(”4’);
cin.read(buf, len);

iostream オブジェクトがマルチスレッドで使用しても安全な場合は、相互排他ロックを行うことで、そのオブジェクトのメンバー変数が保護されます。 しかし、シングルスレッド環境でしか実行されないアプリケーションでは、このロック処理のために、本来なら必要のないオーバーヘッドがかかります。iostream オブジェクトのマルチスレッドでの安全性の有効/無効を動的に切り替えると、パフォーマンスを改善できます。たとえば、iostream オブジェクトのマルチスレッドでの安全性を無効にするには、次のようにします。


例 11–5 マルチスレッドでの安全性の無効化


fs.set_safe_flag(stream_MT::unsafe_object);// disable MT-safety
    .... do various i/o operations

iostream が複数のスレッド間で共有されないコード領域では、マルチスレッドでの安全性の無効化ストリームであっても、安全に使用できます。たとえば、スレッドが 1 つしかないプログラムや、スレッドごとに非公開の iostream を使用するプログラムでは問題は起きません。

プログラムに同期処理を明示的に挿入すると、iostream が複数のスレッド間で共有される場合にも、マルチスレッドで使用すると安全ではない iostream を安全に使用できるようになります。この例を次に示します。


例 11–6 マルチスレッドで使用すると安全ではないオブジェクトの同期処理


    generic_lock();
    fs.set_safe_flag(stream_MT::unsafe_object);
    ... do various i/o operations
    generic_unlock();

ここで、generic_lock 関数と generic_unlock 関数は、相互排他ロック (mutex)、セマフォー、読み取り/書き込みロックといった基本型を使用する同期機能であれば、何でもかまいません。


注 –

libC クラスによって提供される stream_locker クラスは、この目的で優先される機構です。


詳細は 「11.4.5 オブジェクトのロック」を参照してください。