web-dev-qa-db-fra.com

Errno est-il thread-safe?

Dans errno.h, cette variable est déclarée en tant que extern int errno;. Ma question est donc: est-il prudent de vérifier la valeur errno après certains appels ou d'utiliser perror () dans du code multithread. Est-ce une variable thread-safe? Si non, alors quelle est l'alternative? 

J'utilise Linux avec gcc sur une architecture x86. 

150
vinit dhatrak

Oui, c'est sans danger. Sous Linux, la variable errno globale est spécifique à un thread. POSIX exige que errno soit threadsafe.

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

Dans POSIX.1, errno est défini comme un variable globale externe. Mais ça la définition est inacceptable dans un environnement multithread, car son son utilisation peut entraîner une perte de temps déterministe résultats. Le problème est que deux ou plusieurs threads peuvent rencontrer des erreurs, tous provoquant le même errno être mis . Dans ces circonstances, un fil pourrait finir par vérifier errno après a déjà été mis à jour par un autre fil.

Pour contourner le résultat le non-déterminisme, POSIX.1c redéfinit errno en tant que service pouvant accéder au Numéro d'erreur par thread comme suit (ISO/IEC 9945: 1-1996, § 2.4):

Certaines fonctions peuvent fournir le numéro d'erreur dans une variable accédée à travers le symbole errno. Le symbole errno est défini en incluant le en-tête, comme spécifié par le C Standard ... Pour chaque fil de a processus, la valeur de errno ne doit pas être affecté par des appels de fonction ou assignations à errno par d'autres threads.

Voir aussi http://linux.die.net/man/3/errno

errno est thread-local; le placer dans un thread n'affecte pas sa valeur dans les autres threads. 

158
Charles Salvia

Oui


Errno n’est plus une simple variable, c’est quelque chose de complexe en coulisse, spécialement pour le thread-safe.

Voir $ 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.

Nous pouvons vérifier:

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

Dans errno.h, cette variable est déclarée en tant que extern int errno;

Voici ce que dit la norme C:

La macro errno n'est pas nécessairement l'identifiant d'un objet. Elle peut être étendue à une valeur modifiable résultant d'un appel de fonction (par exemple, *errno()).

Généralement, errno est une macro qui appelle une fonction renvoyant l'adresse du numéro d'erreur du thread en cours, puis la déréférence.

Voici ce que j'ai sous Linux, dans /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

Au final, il génère ce genre de 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
10
Bastien Léonard

Sur de nombreux systèmes Unix, la compilation avec -D_REENTRANT garantit que errno est thread-safe.

Par exemple:

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

C'est à partir de <sys/errno.h> sur mon Mac:

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

Donc, errno est maintenant une fonction __error(). La fonction est implémentée de manière à être thread-safe.

8
vy32

yes, comme l'explique la errno man page et les autres réponses, errno est une variable locale du thread. 

Cependant, il y a un détail stupide qui pourrait facilement être oublié. Les programmes doivent enregistrer et restaurer le code d'erreur de tout gestionnaire de signaux exécutant un appel système. Cela est dû au fait que le signal sera traité par l’un des threads de processus qui pourraient écraser sa valeur. 

Par conséquent, les gestionnaires de signaux doivent enregistrer et restaurer errno. Quelque chose comme:

void sig_alarm(int signo)
{
 int errno_save;

 errno_save = errno;

 //whatever with a system call

 errno = errno_save;
}
6

Je pense que la réponse est "ça dépend". Les bibliothèques d'exécution C thread-safe implémentent généralement errno dans un appel de fonction (extension d'une macro à une fonction) si vous créez un code threadé avec les indicateurs corrects.

6
Timo Geusch

Nous pouvons vérifier en exécutant un programme simple sur une 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);                                                                                       
}

En exécutant ce programme et vous pouvez voir différentes adresses pour errno dans chaque thread. La sortie d'une exécution sur ma machine ressemblait à: -

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 

Notez que l'adresse est différente pour tous les threads.

1
Rajat Paliwal