ではerrno.h
、この変数が宣言されているextern int errno;
ので、私の質問は、errno
いくつかの呼び出しの後に値をチェックするか、マルチスレッドコードでperror()を使用しても安全ですか?これはスレッドセーフ変数ですか?そうでない場合、代替案は何ですか?
私はLinuxをx86アーキテクチャのgccで使用しています。
ではerrno.h
、この変数が宣言されているextern int errno;
ので、私の質問は、errno
いくつかの呼び出しの後に値をチェックするか、マルチスレッドコードでperror()を使用しても安全ですか?これはスレッドセーフ変数ですか?そうでない場合、代替案は何ですか?
私はLinuxをx86アーキテクチャのgccで使用しています。
回答:
はい、スレッドセーフです。Linuxでは、グローバルerrno変数はスレッド固有です。POSIXでは、errnoをスレッドセーフにする必要があります。
http://www.unix.org/whitepapers/reentrant.htmlを参照してください
POSIX.1では、errnoは外部グローバル変数として定義されています。ただし、この定義はマルチスレッド環境では受け入れられません。これを使用すると、非決定的な結果が生じる可能性があるためです。問題は、2つ以上のスレッドでエラーが発生し、すべて同じerrnoが設定されることです。これらの状況では、別のスレッドによってすでに更新された後、スレッドがerrnoをチェックしてしまう可能性があります。
結果として生じる非決定性を回避するために、POSIX.1cはerrnoを次のようにスレッドごとのエラー番号にアクセスできるサービスとして再定義します(ISO / IEC 9945:1-1996、§2.4)。
一部の関数は、シンボルerrnoを介してアクセスされる変数にエラー番号を提供する場合があります。シンボルerrnoは、C標準で指定されているようにheaderを含めることで定義されます。プロセスの各スレッドについて、errnoの値は、関数呼び出しや他のスレッドによるerrnoへの割り当ての影響を受けません。
http://linux.die.net/man/3/errnoも参照してください
errnoはスレッドローカルです。1つのスレッドで設定しても、他のスレッドの値には影響しません。
# if !defined _LIBC || defined _LIBC_REENTRANT
通常のプログラムをコンパイルするときに_LIBCが定義されていないはずです。とにかく、echo #include <errno.h>' | gcc -E -dM -xc -
を実行し、-pthreadを使用した場合と使用しない場合の違いを確認します。#define errno (*__errno_location ())
どちらの場合もerrnoです。
Errnoはもはや単純な変数ではなく、舞台裏では特にスレッドセーフであるという点で複雑です。
を参照してください$ man 3 errno
。
ERRNO(3) Linux Programmer’s Manual ERRNO(3)
NAME
errno - number of last error
SYNOPSIS
#include <errno.h>
DESCRIPTION
...
errno is defined by the ISO C standard to be a modifiable lvalue of
type int, and must not be explicitly declared; errno may be a macro.
errno is thread-local; setting it in one thread does not affect its
value in any other thread.
再確認できます:
$ cat > test.c
#include <errno.h>
f() { g(errno); }
$ cc -E test.c | grep ^f
f() { g((*__errno_location ())); }
$
errno.hでは、この変数はextern int errnoとして宣言されています。
C標準の内容は次のとおりです。
マクロ
errno
はオブジェクトの識別子である必要はありません。関数呼び出しの結果、変更可能な左辺値に拡張される場合があります(たとえば、*errno()
)。
一般に、errno
現在のスレッドのエラー番号のアドレスを返す関数を呼び出し、それを逆参照するマクロです。
Linuxの/usr/include/bits/errno.hにあるものは次のとおりです。
/* Function to get address of global `errno' variable. */
extern int *__errno_location (void) __THROW __attribute__ ((__const__));
# if !defined _LIBC || defined _LIBC_REENTRANT
/* When using threads, errno is a per-thread value. */
# define errno (*__errno_location ())
# endif
結局、それはこの種のコードを生成します:
> cat essai.c
#include <errno.h>
int
main(void)
{
errno = 0;
return 0;
}
> gcc -c -Wall -Wextra -pedantic essai.c
> objdump -d -M intel essai.o
essai.o: file format elf32-i386
Disassembly of section .text:
00000000 <main>:
0: 55 push ebp
1: 89 e5 mov ebp,esp
3: 83 e4 f0 and esp,0xfffffff0
6: e8 fc ff ff ff call 7 <main+0x7> ; get address of errno in EAX
b: c7 00 00 00 00 00 mov DWORD PTR [eax],0x0 ; store 0 in errno
11: b8 00 00 00 00 mov eax,0x0
16: 89 ec mov esp,ebp
18: 5d pop ebp
19: c3 ret
多くのUnixシステムでは、でコンパイル-D_REENTRANT
するとerrno
、スレッドセーフになります。
例えば:
#if defined(_REENTRANT) || _POSIX_C_SOURCE - 0 >= 199506L
extern int *___errno();
#define errno (*(___errno()))
#else
extern int errno;
/* ANSI C++ requires that errno be a macro */
#if __cplusplus >= 199711L
#define errno errno
#endif
#endif /* defined(_REENTRANT) */
-D_REENTRANT
。同じ質問については、他の回答に関する議論を参照してください。
-D_XOPEN_SOURCE=500
またはを使用し-D_XOPEN_SOURCE=600
ます。すべての人がPOSIX環境が指定されていることを確認する必要がなく-D_REENTRANT
、ベーコンを節約できるわけではありません。ただし、希望する動作が確実に得られるようにするには、プラットフォームごとに注意が必要です。
errno
タイプint
とスレッドのローカルストレージ期間を持つ変更可能な左辺値(201)に展開され、その値はいくつかのライブラリ関数によって正のエラー番号に設定されます。実際のオブジェクトにアクセスするためにマクロ定義が抑制されている場合、またはプログラムがという名前の識別子を定義しているerrno
場合、動作は未定義です。[...続き...]
errno
ように記載されています。マクロはオブジェクトの識別子である必要はありません。関数呼び出しの結果、変更可能な左辺値に拡張される場合があります(たとえば、*errno()
)。 メインテキストは続きます。初期スレッドのerrnoの値はプログラムの起動時にゼロです(他のスレッドのerrnoの初期値は不定値です)が、ライブラリ関数によってゼロに設定されることはありません。 POSIXはスレッドを認識しなかったC99標準を使用します。[...また継続...]
はい。errnoのマニュアルページとその他の応答で説明されているように、errnoはスレッドローカル変数です。
しかし、簡単に忘れられるかもしれない愚かな細部があります。プログラムは、システムコールを実行するシグナルハンドラのerrnoを保存および復元する必要があります。これは、シグナルがその値を上書きする可能性のあるプロセススレッドの1つによって処理されるためです。
したがって、シグナルハンドラはerrnoを保存および復元する必要があります。何かのようなもの:
void sig_alarm(int signo)
{
int errno_save;
errno_save = errno;
//whatever with a system call
errno = errno_save;
}
答えは「場合によります」と思います。スレッドセーフなCランタイムライブラリは、正しいフラグを使用してスレッド化されたコードを構築している場合、通常、errnoを関数呼び出し(マクロを関数に拡張)として実装します。
マシン上で簡単なプログラムを実行して確認できます。
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#define NTHREADS 5
void *thread_function(void *);
int
main()
{
pthread_t thread_id[NTHREADS];
int i, j;
for(i=0; i < NTHREADS; i++)
{
pthread_create( &thread_id[i], NULL, thread_function, NULL );
}
for(j=0; j < NTHREADS; j++)
{
pthread_join( thread_id[j], NULL);
}
return 0;
}
void *thread_function(void *dummyPtr)
{
printf("Thread number %ld addr(errno):%p\n", pthread_self(), &errno);
}
このプログラムを実行すると、各スレッドでerrnoの異なるアドレスを確認できます。私のマシンでの実行の出力は次のようになりました:
Thread number 140672336922368 addr(errno):0x7ff0d4ac0698
Thread number 140672345315072 addr(errno):0x7ff0d52c1698
Thread number 140672328529664 addr(errno):0x7ff0d42bf698
Thread number 140672320136960 addr(errno):0x7ff0d3abe698
Thread number 140672311744256 addr(errno):0x7ff0d32bd698
アドレスはすべてのスレッドで異なることに注意してください。