この章では、関数とライブラリについて、マルチスレッドに対する安全レベルを定義します。この章では、次の内容について説明します。
「スレッド安全」は、データの競合が回避されていることを示します。データの競合は、複数のスレッドがデータをアクセスして変更するときに、その順番によってデータの値が正しくなったり正しくなくなったりするときに発生します。
スレッド間でデータを共有する必要がない場合は、スレッドごとに専用のコピーを与えますが、共有する必要がある場合には、明示的に同期をとることによってプログラムが確定的な動きをするように制御する必要があります。
手続きが「スレッド安全」とは、その手続きが複数のスレッドによって同時に実行されても論理的な正しさが失われないことです。実際は、安全性は次の 3 段階で区別されます。
「安全ではない」
「スレッド安全」— 直列化
「スレッド安全」— MT-安全
「スレッド安全ではない」手続きであっても、mutex をロックする命令と解除する命令で囲めば、その処理は直列化され「スレッド安全」になります。例 7–1 は fputs() を簡略化したもので、最初のルーチンは「スレッド安全ではない」例です。
2 番目のルーチンは直列化した例です。ここでは、1 つの mutex で手続きを並行実行させないようにしています。この 1 つの mutex は、通常必要とされる同期よりも強い同期となります。2 つのスレッドが fputs() を使って異なるファイルに出力するときは、一方がもう一方を待つ必要はありません。これらのスレッドは、出力ファイルを共有している場合にのみ同期を必要とします。
最後のルーチンは、「MT-安全」の例です。このバージョンでは、ファイルごとに mutex をロックしているので、2 つのスレッドが異なるファイルに同時に出力できます。つまり、ルーチンが「MT-安全」であるとは「スレッド安全」で、しかもそのルーチンの実行が性能に悪影響を及ぼさないということになります。
/* 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) が呼び出されたときに、保持していたロックを解放する |
次の理由により安全化されていない関数もあります。
その関数を「MT-安全」にすると、シングルスレッドアプリケーションの性能に悪影響を及ぼす。
ライブラリが安全でないインタフェースを持っている。たとえば、スタックに確保したバッファーへのポインタを戻すような関数です。こうした関数には、リエントラント (再入可能) な代替関数が用意されている場合があります。再入可能な関数の名前は、元の関数名の末尾に「_r」が追加された形式になっています。
名前の末尾に「_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) |
シグナルハンドラから安全に呼び出すことができる関数を「非同期シグナル安全」関数と呼びます。「IEEE Std 1003.1–2004」(POSIX) 規格には、表 5–2 に示されている「非同期シグナル安全」関数が定義されています。これらの標準の「非同期シグナル安全」関数に加え、Solaris スレッドインタフェースの次の関数も「非同期シグナル安全」関数です。
マルチスレッドプログラムから呼び出される可能性のあるルーチンは、どれも「MT-安全」であるべきです。したがって、同時に呼び出される可能性のあるルーチンは、並行実行されても正しく実行されることが必要です。このため、マルチスレッドプログラムで使用するすべてのライブラリインタフェースは、「MT-安全」でなければなりません。
現状では、すべてのライブラリが「MT-安全」ではありません。次の表に、よく使用される「MT-安全」なライブラリを示します。これらのライブラリは、/usr/lib ディレクトリにあります。
表 7–3 「MT-安全」なライブラリの例
「MT-安全」であることが保証されていないライブラリのルーチンを、マルチスレッドプログラムから安全に呼び出すためには、それらの呼び出しがシングルスレッドで行われるようにしなければなりません。