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

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

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

「スレッド安全」

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

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

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

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

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

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


例 6-1 「スレッド安全」の段階

/* スレッド安全ではない */
fputs(const char *s, FILE *stream) {
    char *p;
    for (p=s; *p; p++)
        putc((int)*p, stream);
}

/* 直列化 */
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-安全 */
mutex_t m[NFILE];
fputs(const char *s, FILE *stream) {
    static mutex_t mut;
    char *p;
    mutex_lock(&m[fileno(stream)]);
    for (p=s; *p; p++)
        putc((int)*p, stream);
    mutex_unlock(&m[fileno(stream)]0;
}

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

SunOS Reference Manual』の Section 3「Library Routines」では、スレッドをサポートするインタフェースを、次のカテゴリに分類しています (これらのカテゴリの詳細は、Intro(3) のマニュアルページを参照してください)。

Safe「安全」 

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

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

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

Unsafe「安全ではない」 

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

MT-Safe「MT-安全」 

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

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

例外については、『SunOS Reference Manual』の Section 3「Library Routines」の「注意事項」の節を参照

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

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

Fork1-Safe「fork1-安全」 

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

付録 C 「「MT-安全」ライブラリインタフェース」 に、『SunOS Reference Manual』の Section 3「Library Routines」に記載されている「安全なインタフェース」の一覧を示します。詳細は、マニュアルページを参照してください。

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


注意 - 注意 -

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


「安全ではない」インタフェースのためのリエントラント関数

危険なインタフェースをもつ多くの関数には、「MT-安全」な代替関数が用意されています。これらの関数は、オリジナルの関数名の末尾に「_r」を付けることで区別されます。Solaris システムに用意されている「_r」ルーチンを表 6-1 に示します。

表 6-1 リエントラントな関数

asctime_r(3c)

gethostbyname_r(3n)

getservbyname_r(3n)

ctermid_r(3s)

gethostent_r(3n)

getservbyport_r(3n)

ctime_r(3c)

getlogin_r(3c)

getservent_r(3n)

fgetgrent_r(3c)

getnetbyaddr_r(3n)

getspent_r(3c)

fgetpwent_r(3c)

getnetbyname_r(3n)

getspnam_r(3c)

fgetspent_r(3c)

getnetent_r(3n)

gmtime_r(3c)

gamma_r(3m)

getnetgrent_r(3n)

lgamma_r(3m)

getauclassent_r(3)

getprotobyname_r(3n)

localtime_r(3c)

getauclassnam_r(3)

getprotobynumber_r(3n)

nis_sperror_r(3n)

getauevent_r(3)

getprotoent_r(3n)

rand_r(3c)

getauevnam_r(3)

getpwent_r(3c)

readdir_r(3c)

getauevnum_r(3)

getpwnam_r(3c)

strtok_r(3c)

getgrent_r(3c)

getpwuid_r(3c)

tmpnam_r(3s)

getgrgid_r(3c)

getrpcbyname_r(3n)

ttyname_r(3c)

getgrnam_r(3c)

getrpcbynumber_r(3n)

 

gethostbyaddr_r(3n)

getrpcent_r(3n)

 

「非同期シグナル安全」関数

「非同期シグナル安全」関数とは、シグナルハンドラから安全に呼び出すことができる関数のことです。それらは、POSIX 規格「IEEE Std 1003.1-1990, 3.3.1.3 (3)(f)」の 55 ページで定義されています。POSIX 規格の「非同期シグナル安全」関数に加え、スレッドライブラリの次の 3 つの関数も「非同期シグナル安全」関数です。

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

マルチスレッドプログラムから呼び出される可能性のあるルーチンは、どれも「MT-安全」であるべきです。

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

現状では、すべてのライブラリが「MT-安全」ではありません。代表的な「MT-安全」ライブラリを表 6-2 に示します。その他のライブラリも、最終的には「MT-安全」なものに修正されます。

表 6-2 「MT-安全」なライブラリの例

ライブラリ 

備考 

lib/libc

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

lib/libdl_stubs

静的スイッチのコンパイルをサポート 

lib/libintl

国際化ライブラリ 

lib/libm

System V Interface Definition、Edition 3、X/Open、および ANSI C に準拠した算術ライブラリ 

lib/libmalloc

空間を効率的に使用したメモリーの割り当てライブラリ。詳細は、malloc(3X) のマニュアルページを参照

lib/libmapmalloc

mmap(2) ベースの代替メモリー割り当てライブラリ。詳細は、mapmalloc(3X) のマニュアルページを参照

lib/libnsl

TLI インタフェース、XDR、RPC クライアントとサーバ、netdirnetselectgetXXbyYY インタフェースは安全ではない。ただし、getXXbyYY_r 形式のインタフェースは 「スレッド安全 」

lib/libresolv

スレッド固有の errno をサポート

lib/libsocket

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

lib/libw

複数バイトロケールをサポートするためのワイド文字とワイド文字列の関数 

lib/straddr

ネットワーク名前アドレス変換ライブラリ 

lib/libX11

X11 ウィンドウライブラリルーチン 

lib/libC

C++ 実行時共有オブジェクト 

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

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