簡體   English   中英

errno是線程安全的嗎?

[英]Is errno thread-safe?

errno.h ,此變量聲明為extern int errno; 所以我的問題是,在某些調用之后檢查errno值還是在多線程代碼中使用perror()是安全的。 這是線程安全變量嗎? 如果沒有,那還有什么選擇呢?

我在x86體系結構上將Linux與gcc一起使用。

是的,它是線程安全的。 在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是線程本地的; 在一個線程中設置它不會影響在其他任何線程中的值。


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

這是來自Mac上的<sys/errno.h>

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

所以errno現在是一個函數__error() 該功能被實現為線程安全的。

,如errno手冊頁和其他答復所述,errno是線程局部變量。

但是 ,有一個愚蠢的細節很容易被遺忘。 程序應在執行系統調用的任何信號處理程序上保存和還原errno。 這是因為信號將由可能覆蓋其值的進程線程之一處理。

因此,信號處理程序應保存並還原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 

請注意,所有線程的地址都不同。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM