简体   繁体   English

errno是线程安全的吗?

[英]Is errno thread-safe?

In errno.h , this variable is declared as extern int errno; errno.h ,此变量声明为extern int errno; so my question is, is it safe to check errno value after some calls or use perror() in multi-threaded code. 所以我的问题是,在某些调用之后检查errno值还是在多线程代码中使用perror()是安全的。 Is this a thread safe variable? 这是线程安全变量吗? If not, then whats the alternative ? 如果没有,那还有什么选择呢?

I am using linux with gcc on x86 architecture. 我在x86体系结构上将Linux与gcc一起使用。

Yes, it is thread safe. 是的,它是线程安全的。 On Linux, the global errno variable is thread-specific. 在Linux上,全局errno变量是特定于线程的。 POSIX requires that errno be threadsafe. POSIX要求errno必须是线程安全的。

See http://www.unix.org/whitepapers/reentrant.html 参见http://www.unix.org/whitepapers/reentrant.html

In POSIX.1, errno is defined as an external global variable. 在POSIX.1中,errno被定义为外部全局变量。 But this definition is unacceptable in a multithreaded environment, because its use can result in nondeterministic results. 但是,此定义在多线程环境中是不可接受的,因为使用它会导致不确定的结果。 The problem is that two or more threads can encounter errors, all causing the same errno to be set. 问题是两个或多个线程可能会遇到错误,所有错误都会导致设置相同的错误号。 Under these circumstances, a thread might end up checking errno after it has already been updated by another thread. 在这种情况下,一个线程可能已经被另一个线程更新后,最终检查errno。

To circumvent the resulting nondeterminism, POSIX.1c redefines errno as a service that can access the per-thread error number as follows (ISO/IEC 9945:1-1996, §2.4): 为了避免产生不确定性,POSIX.1c将errno重新定义为可以访问每个线程错误号的服务,如下所示(ISO / IEC 9945:1-1996,§2.4):

Some functions may provide the error number in a variable accessed through the symbol errno. 某些函数可能在通过符号errno访问的变量中提供错误号。 The symbol errno is defined by including the header , as specified by the C Standard ... For each thread of a process, the value of errno shall not be affected by function calls or assignments to errno by other threads. errno符号是通过包括C标准所指定的标头定义的。对于进程的每个线程,errno的值均不受其他线程对函数的调用或对errno的分配的影响。

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

errno is thread-local; errno是线程本地的; setting it in one thread does not affect its value in any other thread. 在一个线程中设置它不会影响在其他任何线程中的值。

Yes


Errno isn't a simple variable anymore, it's something complex behind the scenes, specifically for it to be thread-safe. Errno不再是一个简单的变量,它是幕后复杂的事情,特别是它具有线程安全性。

See $ man 3 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.

We can double-check: 我们可以仔细检查:

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

In errno.h, this variable is declared as extern int errno; 在errno.h中,此变量声明为extern int errno;

Here is what the C standard says: 这是C标准所说的:

The macro errno need not be the identifier of an object. errno不必是对象的标识符。 It might expand to a modifiable lvalue resulting from a function call (for example, *errno() ). 它可能会扩展为由函数调用(例如*errno() )导致的可修改的左值。

Generally, errno is a macro which calls a function returning the address of the error number for the current thread, then dereferences it. 通常, errno是一个宏,该宏调用一个函数,该函数返回当前线程的错误号的地址,然后对其进行取消引用。

Here is what I have on Linux, in /usr/include/bits/errno.h: 这是我在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

In the end, it generates this kind of code: 最后,它将生成这种代码:

> 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

On many Unix systems, compiling with -D_REENTRANT ensures that errno is thread-safe. 在许多Unix系统上,使用-D_REENTRANT可确保errno是线程安全的。

For example: 例如:

#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) */

This is from <sys/errno.h> on my Mac: 这是来自Mac上的<sys/errno.h>

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

So errno is now a function __error() . 所以errno现在是一个函数__error() The function is implemented so as to be thread-safe. 该功能被实现为线程安全的。

yes , as it is explained in the errno man page and the other replies, errno is a thread local variable. ,如errno手册页和其他答复所述,errno是线程局部变量。

However , there is a silly detail which could be easily forgotten. 但是 ,有一个愚蠢的细节很容易被遗忘。 Programs should save and restore the errno on any signal handler executing a system call. 程序应在执行系统调用的任何信号处理程序上保存和还原errno。 This is because the signal will be handled by one of the process threads which could overwrite its value. 这是因为信号将由可能覆盖其值的进程线程之一处理。

Therefore, the signal handlers should save and restore errno. 因此,信号处理程序应保存并还原errno。 Something like: 就像是:

void sig_alarm(int signo)
{
 int errno_save;

 errno_save = errno;

 //whatever with a system call

 errno = errno_save;
}

I think the answer is "it depends". 我认为答案是“取决于”。 Thread-safe C runtime libraries usually implement errno as a function call (macro expanding to a function) if you're building threaded code with the correct flags. 如果使用正确的标志构建线程代码,则线程安全的C运行时库通常将errno实现为函数调用(宏扩展为函数)。

We can check by running a simple program on a machine. 我们可以通过在机器上运行一个简单程序来进行检查。

#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);                                                                                       
}

Running this program and you can see different addresses for errno in each thread. 运行该程序,您可以在每个线程中看到errno的不同地址。 The output of a run on my machine looked like:- 我的机器上运行的输出看起来像:-

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 

Notice that address is different for all threads. 请注意,所有线程的地址都不同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM