errno线程安全吗?

vin*_*rak 163 c linux multithreading gcc

errno.h,这个变量被声明为extern int errno;我的问题是,errno在一些调用之后检查值是否安全或在多线程代码中使用perror()是否安全.这是一个线程安全变量吗?如果没有,那么替代方案是什么?

我在x86架构上使用linux和gcc.

Cha*_*via 168

是的,它是线程安全的.在Linux上,全局errno变量是特定于线程的.POSIX要求errno是线程安全的.

http://www.unix.org/whitepapers/reentrant.html

在POSIX.1中,errno被定义为外部全局变量.但是这个定义在多线程环境中是不可接受的,因为它的使用会导致不确定的结果.问题是两个或多个线程可能遇到错误,所有错误都会导致设置相同的错误.在这种情况下,一个线程可能会在另一个线程更新之后最终检查errno.

为了规避由此产生的非确定性,POSIX.1c将errno重新定义为可以按如下方式访问每线程错误号的服务(ISO/IEC 9945:1-1996,§2.4):

某些函数可以在通过符号errno访问的变量中提供错误号.符号errno通过包含标头来定义,如C标准所规定的......对于进程的每个线程,errno的值不受其他线程的函数调用或对errno的赋值的影响.

另请参阅http://linux.die.net/man/3/errno

errno是线程本地的; 在一个线程中设置它不会影响其在任何其他线程中的值.

  • 真?他们什么时候这样做的?当我做C编程时,信任errno是一个大问题. (9认同)
  • 男人,那会在我的日子里省去很多麻烦. (7认同)
  • @vinit:errno实际上是在bits/errno.h中定义的.阅读包含文件中的注释.它说:"声明`errno'变量,除非它被bits/errno.h定义为宏.这是GNU中的情况,它是一个每线程变量.使用宏的重新声明仍然有效,但它将是一个没有原型的函数声明,可能会触发-Wstrict-prototypes警告." (4认同)
  • @vinit dhatrak应该有`#if!defined _LIBC || 定义_LIBC_REENTRANT`时,在编译普通程序时未定义_LIBC.无论如何,运行echo`#include <errno.h>'| gcc -E -dM -xc - `并查看使用和不使用-pthread的区别.在两种情况下,errno都是`#define errno(*__ errno_location())`. (3认同)
  • 如果您使用的是Linux 2.6,则无需执行任何操作。刚开始编程。:-) (2认同)
  • 嗯..但是只有当 `define _LIBC_REENTRANT` 为 true 时,才会定义 errno 宏。所以我想,当我们用“lpthread”编译代码时,只有这个宏才会被启用。如果我在这里错了,请纠正我。 (2认同)
  • 考虑添加 C11 标准(不是更早的)的摘录,`7.5 Errors &lt;errno.h&gt;`:它也保证了线程安全。 (2认同)

Dig*_*oss 57


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.
Run Code Online (Sandbox Code Playgroud)

我们可以仔细检查:

$ cat > test.c
#include <errno.h>
f() { g(errno); }
$ cc -E test.c | grep ^f
f() { g((*__errno_location ())); }
$ 
Run Code Online (Sandbox Code Playgroud)


Bas*_*ard 12

在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
Run Code Online (Sandbox Code Playgroud)

最后,它会生成这种代码:

> 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
Run Code Online (Sandbox Code Playgroud)


Jon*_*ler 10

在许多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) */
Run Code Online (Sandbox Code Playgroud)

  • @Vinit:这取决于你的平台 - 在Linux上,你可能是正确的; 在Solaris上,只有将_POSIX_C_SOURCE设置为199506或更高版本时才会正确 - 可能是使用`-D_XOPEN_SOURCE = 500`或`-D_XOPEN_SOURCE = 600`.不是每个人都想确保指定POSIX环境 - 然后`-D_REENTRANT`可以保存您的培根.但是你仍然必须小心 - 在每个平台上 - 以确保你获得所需的行为. (3认同)

vy3*_*y32 9

这是来自<sys/errno.h>我的Mac:

#include <sys/cdefs.h>
__BEGIN_DECLS
extern int * __error(void);
#define errno (*__error())
__END_DECLS
Run Code Online (Sandbox Code Playgroud)

所以errno现在是一个功能__error().实现该功能以便是线程安全的.


mar*_*ril 8

是的,正如在errno手册页和其他回复中所解释的那样,errno是一个线程局部变量.

但是,有一个很容易被遗忘的愚蠢细节.程序应该在执行系统调用的任何信号处理程序上保存和恢复errno.这是因为信号将由其中一个可能覆盖其值的进程线程处理.

因此,信号处理程序应该保存并恢复errno.就像是:

void sig_alarm(int signo)
{
 int errno_save;

 errno_save = errno;

 //whatever with a system call

 errno = errno_save;
}
Run Code Online (Sandbox Code Playgroud)


小智 8

我们可以通过在机器上运行一个简单的程序来检查。

#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);                                                                                       
}
Run Code Online (Sandbox Code Playgroud)

运行这个程序,你可以看到每个线程中 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 
Run Code Online (Sandbox Code Playgroud)

请注意,所有线程的地址都不同。


Tim*_*sch 6

我认为答案是"它取决于".线程安全的C运行时库通常将errno实现为函数调用(宏扩展到函数),如果您使用正确的标志构建线程代码.