errno是线程安全的吗?
在errno.h
,这个variables被声明为extern int errno;
所以我的问题是,在multithreading代码中调用或使用perror()后检查errno
值是否安全。 这是一个线程安全variables? 如果不是,那么最后的select是什么?
我在x86架构上使用linux和gcc。
是的,这是线程安全的。 在Linux上,全局errnovariables是线程特定的。 POSIX要求errno是线程安全的。
见http://www.unix.org/whitepapers/reentrant.html
在POSIX.1中,errno被定义为外部全局variables。 但是这个定义在multithreading环境中是不可接受的,因为它的使用可能会导致不确定的结果。 问题是两个或多个线程可能会遇到错误,所有导致相同的错误设置。 在这种情况下,一个线程可能在已经被另一个线程更新之后检查errno。
为了规避由此产生的不确定性,POSIX.1c将errno重新定义为一个可以访问每个线程错误号的服务(ISO / IEC 9945:1-1996,§2.4):
某些函数可能会在通过符号errno访问的variables中提供错误编号。 符号errno是通过包含由C标准规定的头来定义的…对于进程的每个线程来说,errno的值不会被其他线程的函数调用或分配给errno。
另请参阅http://linux.die.net/man/3/errno
errno是线程本地的; 在一个线程中设置它不会影响其在任何其他线程中的值。
是
Errno不再是一个简单的variables,它在幕后非常复杂,特别是它是线程安全的。
看$ 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中,这个variables被声明为extern int errno;
这是C标准所说的:
macros
errno
不必是一个对象的标识符。 它可能会扩展为由函数调用(例如,*errno()
)产生的可修改的左值。
通常, errno
是一个调用返回当前线程错误号地址的函数的macros,然后对其进行解引用。
这是我在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) */
我认为答案是“视情况而定”。 如果使用正确的标志构build线程代码,线程安全的C运行时库通常将errno作为函数调用(macros扩展为函数)来实现。
这是从我的Mac上的<sys/errno.h>
:
#include <sys/cdefs.h> __BEGIN_DECLS extern int * __error(void); #define errno (*__error()) __END_DECLS
所以errno
现在是一个函数__error()
。 该函数被实现为线程安全的。
是的 ,正如在errno手册页和其他答复中所解释的,errno是一个线程局部variables。
但是 ,有一个很容易被遗忘的愚蠢的细节。 程序应该在执行系统调用的任何信号处理程序上保存和恢复errno。 这是因为信号将被其中一个可能覆盖其值的进程线程处理。
因此,信号处理程序应该保存并恢复errno。 就像是:
void sig_alarm(int signo) { int errno_save; errno_save = errno; //whatever with a system call errno = errno_save; }