2.2.6. ARM C ライブラリでのスレッドセーフティ

ARM ライブラリ内にある関数のスレッドセーフティは、以下のようになっています。

ARM ライブラリを表面に出さない形式でアプリケーションから利用する場合、例えば、言語ヘルパ関数を使用する場合などは、スレッド問題が発生する可能性があります。

スレッドセーフな関数

Table 2.1 に、スレッドセーフな C ライブラリ関数を示します。

Table 2.1. スレッドセーフな関数

関数説明
calloc(), free(), malloc(),
realloc()

ヒープ関数はスレッドセーフです。

すべてのスレッドで 1 つのヒープが共有されます。同時アクセスが発生する場合は、データの破損を防ぐために相互排除関数が使用されます。 ヒープに関するロック処理は、個々のヒープ実装自体によって固有の方法で行われます。 ユーザが独自にアロケータを指定する場合は、そのアロケータでも独自のロック処理を行う必要があります。 これにより、単一の相互排除関数を使用して単純にヒープ全体を保護する(粗粒ロック)のではなく、必要に応じて細粒ロックを行うことができます。

__alloca(), __alloca_finish(),
__alloca_init(),
__alloca_initialize()

alloca 関数はスレッドセーフです。

各スレッドの alloca 状態は、__user_perthread_libspace ブロックに格納されます。 これにより、複数のスレッド間での競合の発生を回避できます。

Note

alloca 関数はヒープも使用します。 ただし、ヒープ関数はすべてスレッドセーフです。

abort(), raise(), signal(),
fenv.h

ARM シグナル処理関数と FP 例外トラップはスレッドセーフです。

シグナルハンドラおよび FP トラップの設定はプロセス全体を通じてグローバルです。また、これらの設定は、ロックによって保護されています。 複数のスレッドによって signal() 関数または fenv.h 関数が同時に呼び出されても、データは破損しません。 ただし、関数呼び出しの影響は、呼び出し側スレッドだけではなく、すべてのスレッドに及ぶので注意して下さい。

clearerr(), fclose(), feof(),
ferror(), fflush(), fgetc(),
fgetpos(), fgets(), fopen(),
fputc(), fputs(), fread(),
freopen(), fseek(), fsetpos(),
ftell(), fwrite(), getc(),
getchar(), gets(), perror(),
putc(), putchar(), puts(),
rewind(), setbuf(), setvbuf(),
tmpfile(), tmpnam(), ungetc()

stdio ライブラリはスレッドセーフです。

個々のストリームはロックにより保護されているので、2 つのスレッドは互いに割り込むことなく、各自 stdio ストリームを開いて利用できます。

2 つのスレッドによって同じストリームの読み出しまたは書き込みが実行される場合は、fgetc() および fputc() レベルでロックすることによりデータ破損を回避できます。ただし、各スレッドによって出力される文字列が個々の文字単位で混じり合い読みにくくなる可能性があります。

Note

tmpnam() 関数にはスタティックバッファも格納されますが、使用されるのは引数が NULL の場合だけです。 使用する tmpnam() を確実にスレッドセーフにするには、独自のバッファスペースを指定します。

fprintf(), printf(), vfprintf(),
vprintf(), fscanf(), scanf()

これらの関数を使用した結果は、以下のとおりです。

  • 標準 C の printf() および scanf() 関数は stdio を使用します。したがって、これらの関数はスレッドセーフです。

  • マルチスレッドプログラムで呼び出された場合、標準 C の printf() 関数はロケール設定の変更の影響を受けます。

clock()
clock() には、プログラム起動時に一度書き込まれると、その後は読み出し専用となるスタティックデータが格納されます。 したがって、ライブラリ初期化時にまだ別のスレッドが実行されていなければ、clock() 関数はスレッドセーフです。
errno()

errno はスレッドセーフです。

各スレッドはそれぞれ独自の errno__user_perthread_libspace ブロックに保存しています。 そのため、各スレッドは、errno 設定関数を個別に呼び出してから、他のスレッドに割り込むことなく errno をチェックできます。

atexit()

atexit() が維持する終了関数のリストは、プロセス内でグローバルです。また、ロックによって保護されています。

最悪の場合、複数のスレッドが atexit() を呼び出した場合、呼び出される終了関数の順序は保証されません。

abs(), acos(), asin(),
atan(), atan2(), atof(),
atol(), atoi(), bsearch(),
ceil(), cos(), cosh(),
difftime(), div(), exp(),
fabs(), floor(), fmod(),
frexp(), labs(), ldexp(),
ldiv(), log(), log10(),
memchr(), memcmp(), memcpy(),
memmove(), memset(), mktime(),
modf(), pow(), qsort(),
sin(), sinh(), sqrt(),
strcat(), strchr(), strcmp(),
strcpy(), strcspn(), strlcat(),
strlcpy(), strlen(), strncat(),
strncmp(), strncpy(), strpbrk(),
strrchr(), strspn(), strstr(),
strxfrm(), tan(), tanh()

これらの関数はスレッドセーフです。

longjmp(), setjmp()

setjmp() および longjmp() 関数は、データを __user_libspace に保持しますが、スレッドセーフな __alloca_* 関数を呼び出します。

remove(), rename(), time()

これらの関数は、ARM デバッグ環境と通信する割り込みを使用します。 通常、実際のアプリケーションではこれらの関数を再実装する必要があります。

snprintf(), sprintf(), vsnprintf(),
vsprintf(), sscanf(), isalnum(),
isalpha(), iscntrl(), isdigit(),
isgraph(), islower(), isprint(),
ispunct(), isspace(), isupper(),
isxdigit(), tolower(), toupper(),
strcoll(), strtod(), strtol(),
strtoul(), strftime()

これらの関数を使用した場合、文字列ベース関数はロケール設定を読み出します。 通常、これらの関数はスレッドセーフです。 ただし、セッションの途中でロケールを変更する場合は、これらの関数に影響が及ばないようにする必要があります。

sprintf()sscanf() などの文字列ベース関数は、stdio ライブラリを使用しません。

stdin, stdout, stderr
これらの関数はスレッドセーフです。
FP ステータスワード

FP ステータスワードは、マルチスレッド環境や、ソフトウェア浮動小数点でも安全に使用できます。 ここで取り上げる、各スレッドのステータスワードは、スレッド独自の __user_perthread_libspace ブロックに保存されます。

Note

ハードウェア浮動小数点では、FP ステータスワードは VFP レジスタに保存されます。 この場合、スレッド切り替えメカニズムは、スレッドごとに、VFP レジスタのコピーを保持する必要があります。

スレッドセーフではない関数

Table 2.2 に、スレッドセーフでない C ライブラリ関数を示します。

Table 2.2. スレッドセーフではない関数

関数説明
setlocale()

ロケール設定はすべてのスレッドを通じてグローバルであり、ロックによって保護されていません。 2 つのスレッドが setlocale() を呼び出すと、データが破損する可能性があります。 また、strtod()sprintf() など、その他の多くの関数も、現在のロケール設定を読み出します。 したがって、1 つのスレッドが他のスレッドと同時に setlocale() を呼び出すと、予期しない結果が発生する場合があります。

使用するロケールを 1 つに決定し、setlocale() を 1 回だけ呼び出してロケールを初期化することをお勧めします。 これは、プログラムで追加スレッドを作成する前に行って下さい。これにより、複数のスレッドが相互に干渉することなく同じロケール設定を同時に読み出すことができます。

localeconv() はスレッドセーフではありません。 この関数の代わりに、ユーザ指定バッファを指すポインタを使用して ARM 関数 _get_lconv() を呼び出して下さい。

asctime(), localtime(), strtok()

これらの関数は、いずれもスレッドセーフではありません。 各関数には、スタティックバッファが含まれています。スタティックバッファは、関数の呼び出しとその戻り値の使用の間に別のスレッドによって上書きされる可能性があります。

ARM には、POSIX によって規定されている再入可能バージョン、すなわち、asctime_r()localtime_r()strtok_r() が用意されています。 安全性を確保するためにこれらの関数を使用することをお勧めします。

Note

これらの再入可能関数は、次のトークンを指す char ポインタへのポインタである追加のパラメータを取ります。

gamma(), lgamma()
これらの拡張 mathlib 関数は、math.h 内で定義されており、グローバル変数 signgam を使用するので、スレッドセーフではありません これらの関数の代わりに gamma_r() および lgamma_r() 関数を使用することをお勧めします。 これらの関数は、指定された int * 引数によって参照されるメモリに結果の符号を書き込みます。
mbrlen(), mbsrtowcs(), mbrtowc(),
wcrtomb(), wcsrtombs()

C89 マルチバイト変換関数(stdlib.h に定義)は、すべてのスレッドにロックなしで共有される内部スタティック状態を保持しているので、スレッドセーフではありません。このような関数として mblen() および mbtowc() があります。

ただし、拡張再起動可能バージョン(wchar.h に定義)である mbrtowc()wcrtomb() などは、独自に作成した mbstate_t オブジェクトへのポインタを渡す場合にはスレッドセーフです。 マルチバイト文字列を処理する場合にスレッドセーフティを確保するには、これらの関数を非 NULL mbstate_t * パラメータと一緒に排他的に使用する必要があります。

exit()

マルチスレッドプログラムでは、exit() 関数を呼び出さないで下さい(すべてのスレッドを停止する、基礎となる _sys_exit() の実装を指定していても呼び出さないで下さい)。

これを守らないと、exit() が呼び出される前に _sys_exit() がクリアされ、他のスレッドに対する割り込みが発生します。

rand(), srand()

これらの関数は、保護されていないグローバルな内部状態を維持します。 つまり、rand() の呼び出しはスレッドセーフではありません。

一度に 1 つのスレッドのみが rand() を呼び出すように独自のロックを適用することをお勧めします。コードを変更せずロックを適用するには、$Sub$$rand() を定義します。

または、以下のいずれかを実行します。

  • 複数の独立したインスタンスを持つことができる、独自の乱数ジェネレータを指定します。

  • 1 つのスレッドのみが乱数を生成するようにスレッドを編成します。

Copyright © 2007 ARM Limited. All rights reserved.ARM DUI 0349AJ
Non-Confidential