マルチスレッドのプログラミング

第 7 章 安全なインタフェースと安全ではないインタフェース

この章では、関数とライブラリについて、マルチスレッドに対する安全レベルを定義します。この章では、次の内容について説明します。

スレッド安全

「スレッド安全」は、データの競合が回避されていることを示します。データの競合は、複数のスレッドがデータをアクセスして変更するときに、その順番によってデータの値が正しくなったり正しくなくなったりするときに発生します。

スレッド間でデータを共有する必要がない場合は、スレッドごとに専用のコピーを与えますが、共有する必要がある場合には、明示的に同期をとることによってプログラムが確定的な動きをするように制御する必要があります。

手続きが「スレッド安全」とは、その手続きが複数のスレッドによって同時に実行されても論理的な正しさが失われないことです。実際は、安全性は次の 3 段階で区別されます。

「スレッド安全ではない」手続きであっても、mutex をロックする命令と解除する命令で囲めば、その処理は直列化され「スレッド安全」になります。例 7–1fputs() を簡略化したもので、最初のルーチンは「スレッド安全ではない」例です。

2 番目のルーチンは直列化した例です。ここでは、1 つの mutex で手続きを並行実行させないようにしています。この 1 つの mutex は、通常必要とされる同期よりも強い同期となります。2 つのスレッドが fputs() を使って異なるファイルに出力するときは、一方がもう一方を待つ必要はありません。これらのスレッドは、出力ファイルを共有している場合にのみ同期を必要とします。

最後のルーチンは、「MT-安全」の例です。このバージョンでは、ファイルごとに mutex をロックしているので、2 つのスレッドが異なるファイルに同時に出力できます。つまり、ルーチンが「MT-安全」であるとは「スレッド安全」で、しかもそのルーチンの実行が性能に悪影響を及ぼさないということになります。


例 7–1 「スレッド安全」の段階

/* not thread-safe */
fputs(const char *s, FILE *stream) {
    char *p;
    for (p=s; *p; p++)
        putc((int)*p, stream);
}

/* serializable */
fputs(const char *s, FILE *stream) {
    static mutex_t mut;
    char *p;
    mutex_lock(&m);
    for (p=s; *p; p++)
        putc((int)*p, stream);

    mutex_unlock(&m);
}

/* MT-Safe */
mutex_t m[NFILE];
fputs(const char *s, FILE *stream) {
    char *p;
    mutex_lock(&m[fileno(stream)]);
    for (p=s; *p; p++)
        putc((int)*p, stream);
    mutex_unlock(&m[fileno(stream)]0;
}

マルチスレッドインタフェースの安全レベル

関数やインタフェースのマニュアルページは、その関数またはインタフェースでのスレッドのサポート範囲を示しています。各マニュアルページの「ATTRIBUTES」セクションには、表 7–1 の安全レベルのカテゴリのいずれかに設定された MT レベルの属性が示されています。これらのカテゴリの詳細は、attributes(5) のマニュアルページを参照してください。

マニュアルページに関数が「MT-安全」であることが明示的に記載されていない場合、その関数は安全でないと見なす必要があります。

表 7–1 インタフェースの安全レベル

カテゴリ 

説明 

Safe 「安全」 

このコードをマルチスレッドアプリケーションから呼び出しても安全 

Safe with exceptions「例外付きで安全」 

例外の内容については、マニュアルページの「注意事項 (NOTES)」の節を参照 

「安全ではない」 

このインタフェースをマルチスレッドアプリケーションで使用するのは危険。ただし、複数のスレッドが、ライブラリ内で同時に実行されないようにアプリケーション側が対応すれば使用できる 

MT-Safe「MT-安全」 

このインタフェースは、マルチスレッドアクセスに完全に対応している。このインタフェースは、安全であると同時に並行性もサポートしている

MT-Safe with exceptions「例外付きで MT-安全」 

例外の内容については、マニュアルページの「注意事項 (NOTES)」の節を参照 

Async-Signal-Safe「非同期シグナル安全」 

このルーチンをシグナルハンドラから安全に呼び出すことができる。「非同期シグナル安全」ルーチンは、シグナルが割り込んでも自己デッドロックにならない「Solaris スレッドでの「非同期シグナル安全」関数」を参照してください。

「fork1-安全」 

このときインタフェースは、Solaris の fork1(2) または POSIX の fork(2) が呼び出されたときに、保持していたロックを解放する

次の理由により安全化されていない関数もあります。


注 –

名前の末尾に「_r」が付いていない関数がマルチスレッドに対して安全かどうかは、その関数のマニュアルページで確認してください。「MT-安全」ではないことが明記されている関数は、同期機構で保護するか、初期スレッド以外では使用しないでください。


安全でないインタフェースのための再入可能な関数

危険なインタフェースをもつ多くの関数には、「MT-安全」な代替関数が用意されています。「MT-安全」なルーチンの名前は、安全でないルーチンの名前の末尾に「_r」が追加された形式になっています。たとえば、asctime() の「MT-安全」なバージョンは asctime_r() です。表 7–2 に、Solaris 環境に用意されている「_r」ルーチンを示します。

表 7–2 再入可能な関数

asctime_r(3c)

gethostbyname_r(3nsl)

getservbyname_r(3socket)

ctermid_r(3c)

gethostent_r(3nsl)

getservbyport_r(3socket)

ctime_r(3c)

getlogin_r(3c)

getservent_r(3socket)

fgetgrent_r(3c)

getnetbyaddr_r(3socket)

getspent_r(3c)

fgetpwent_r(3c)

getnetbyname_r(3socket)

getspnam_r(3c)

fgetspent_r(3c)

getnetent_r(3socket)

gmtime_r(3c)

gamma_r(3m)

getnetgrent_r(3c)

lgamma_r(3m)

getauclassent_r(3bsm)

getprotobyname_r(3socket)

localtime_r(3c)

getauclassnam_r(3bsm)

getprotobynumber_r(3socket)

nis_sperror_r(3nsl)

getauevent_r(3bsm)

getprotoent_r(3socket)

rand_r(3c)

getauevnam_r(3bsm)

getpwent_r(3c)

readdir_r(3c)

getauevnum_r(3bsm)

getpwnam_r(3c)

strtok_r(3c)

getgrent_r(3c)

getpwuid_r(3c)

tmpnam_r(3c)

getgrgid_r(3c)

getrpcbyname_r(3nsl)

ttyname_r(3c)

getgrnam_r(3c)

getrpcbynumber_r(3nsl)

 

gethostbyaddr_r(3nsl)

getrpcent_r(3nsl)

 

Solaris スレッドでの「非同期シグナル安全」関数

シグナルハンドラから安全に呼び出すことができる関数を「非同期シグナル安全」関数と呼びます。「IEEE Std 1003.1–2004」(POSIX) 規格には、表 5–2 に示されている「非同期シグナル安全」関数が定義されています。これらの標準の「非同期シグナル安全」関数に加え、Solaris スレッドインタフェースの次の関数も「非同期シグナル安全」関数です。

ライブラリの「MT-安全」レベル

マルチスレッドプログラムから呼び出される可能性のあるルーチンは、どれも「MT-安全」であるべきです。したがって、同時に呼び出される可能性のあるルーチンは、並行実行されても正しく実行されることが必要です。このため、マルチスレッドプログラムで使用するすべてのライブラリインタフェースは、「MT-安全」でなければなりません。

現状では、すべてのライブラリが「MT-安全」ではありません。次の表に、よく使用される「MT-安全」なライブラリを示します。これらのライブラリは、/usr/lib ディレクトリにあります。

表 7–3 「MT-安全」なライブラリの例

ライブラリ 

コメント 

libc

安全でないインタフェースの「スレッド安全」形式には、対応する「*_r」(セマンティクスが異なることが多い) 形式の「スレッド安全」なインタフェースがある

libm

System V Interface Definition 第 3 版、X/Open および ANSI C に準拠した数学ライブラリ

libmalloc

領域を効果的に使用するメモリーの割り当てライブラリ。malloc(3MALLOC) を参照

libmapmalloc

mmap を使用した代替メモリー割り当てライブラリ。mapmalloc(3MALLOC) を参照

libnsl

TLI インタフェース、XDR、RPC クライアントとサーバー、netdirnetselect、および getXXbyYY インタフェースは安全ではないが、対応する getXXbyYY_r 形式のスレッド安全なインタフェースがある

libresolv

ドメインネームサーバーのライブラリルーチン

libsocket

ネットワーク接続用のソケットライブラリ

libX11

X11 ウィンドウシステムライブラリルーチン

libCrun

Sun C++ 5.0 コンパイラ用の C++ 実行時共有オブジェクト

libCstd

Sun C++ 5.0 コンパイラ用の C++ 標準ライブラリ

libiostream

Sun C++ 5.0 コンパイラ用の古典的 iostream ライブラリ

libC.so.5

Sun C++ 4.0 コンパイラ用の C++ 実行時および iostream ライブラリ

「スレッド安全ではない」ライブラリ

「MT-安全」であることが保証されていないライブラリのルーチンを、マルチスレッドプログラムから安全に呼び出すためには、それらの呼び出しがシングルスレッドで行われるようにしなければなりません。