スレッドセーフ
読み:スレッドセーフ
外語:thread safe
マルチスレッド環境で、複数のスレッドから呼ばれても問題が生じない作りになっていること。
概要
スレッドセーフでないと、複数のスレッドから同時に利用された場合にバッファー/オブジェクト等が破壊されるなどし、正常に動作できなくなる。
Cの標準関数でもスレッドセーフでないものは数多くある。静的データへのポインターを返すものは全てスレッドセーフではなく、関数の戻り値を得てから使用するまでの間に別のスレッドによって結果が上書きされる可能性がある。
特徴
関数
スレッドセーフでないものとしては、C言語のstrtok()が有名。マルチスレッド処理でこの関数が使われ、発見しにくいバグを混入させる事件が今も度々発生している(Androidなどにも少なからず存在する)。
従って、スレッドセーフではない関数は絶対に使っていはいけない。
スレッドセーフではないCライブラリ関数として、次のようなものがある。置き換え先となる再入可能関数を併記する。
- <time.h>
- asctime()、ctime()、gmtime()、localtime() これら関数は、静的データへのポインターを返すので、スレッドセーフではない。
→ asctime_r()、ctime_r()、gmtime_r()、localtime_r() SUS v2以降で対応
- <string.h>
- strtok() 内部でstatic変数またはグローバル変数に状態を保持しているため、スレッドセーフではない。
→ strtok_r() POSIX.1-2001以降で対応
- <stdlib.h>
- exit() 保護されていないグローバル変数を使用しているため、スレッドセーフではない。
環境に応じた関数を使用する。
- rand()、srand() 保護されていないグローバル変数を使用しているため、スレッドセーフではない。
→ rand_r() POSIX.1-2001、ただし POSIX.1-2009 では将来的に廃止予定としている。後述の関数を使用するのが良い
→ random()、srandom()、initstate()、setstate() 4.3BSD、POSIX.1-2001
- <math.h>
- gamma() または lgamma()、lgammaf()、lgammal() 内部でstatic変数またはグローバル変数に状態を保持しているため、スレッドセーフではない。
→ lgamma_r()、lgammaf_r()、lgammal_r() 非標準の拡張
- <wchar.h>
- mbrlen()、mbsrtowcs()、mbrtowc()、wcrtomb()、wcsrtombs() いずれも制限付きのスレッドセーフ関数である。
各関数の最後のパラメーター mbstate_t * をNULLにした場合、内部でstatic変数またはグローバル変数に状態を保持するため、スレッドセーフではなくなる。
→ 必ず mbstate_t * を設定する
- <locale.h>
- setlocale() ロケールは、システム全体を通してグローバルであり、ロックでの保護もない。複数のスレッドから設定や参照などをすると、データが破損する可能性や、予期せぬ結果となる場合がある。
→ 実装によっては、再入可能バージョンの独自関数を提供していることがある
- localeconv() この関数は、静的データへのポインターを返すので、スレッドセーフではない。
→ 実装によっては、再入可能バージョンの独自関数を提供していることがある
グローバル変数
ミューテックスなど適切なロックをせずにグローバル変数を使用した場合、その変数がある特定の読み出し専用用途でない限り、そのコードはスレッドセーフではなくなる。
なお、グローバル変数に見えるerrnoは実はマクロであり、グローバル変数ではなく各スレッド毎に値を持っている。従って、同じプロセスでもスレッドが異なれば別の変数である。
再検索