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.
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.
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 ())); }
$
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
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) */
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.
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;
}
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.
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.