Является ли errno потокобезопасным?

В errno.h эта переменная объявляется как extern int errno;, поэтому мой вопрос: безопасно ли проверять значение errno после некоторых вызовов или использовать perror() в многопоточном коде. Является ли это переменной, защищенной потоком? Если нет, то какая альтернатива?

Я использую linux с gcc на архитектуре x86.

Ответ 1

Да, это поточно-безопасный. В Linux глобальная переменная errno связана с потоком. POSIX требует, чтобы errno был потокобезопасным.

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

В POSIX.1 errno определяется как внешняя глобальная переменная. Но это определение неприемлемо в многопоточной среде, поскольку ее использование может привести к недетерминированному Результаты. Проблема в том, что два или больше потоков может столкнуться с ошибками, все вызывая то же самое errno. В этих условиях поток может закончиться проверкой errno после него уже обновлен другим нить.

Чтобы обойти полученный результат недетерминизм, POSIX.1c переопределяет errno как служба, которая может получить доступ к номер ошибки для каждой строки. (ИСО/МЭК 9945: 1-1996, §2.4):

Некоторые функции могут предоставлять номер ошибки в переменной, доступной через символ errno. Символ errno определяется включением заголовок, как указано в C Стандарт... Для каждого потока процесс, значение errno не должно зависят от вызовов функций или присвоения errno другими потоками.

Также см. http://linux.die.net/man/3/errno

errno является поточно-локальным; установка его в один поток не влияет на его значение в любом другом потоке.

Ответ 2

Да


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

Ответ 3

В errno.h эта переменная объявляется как extern int errno;

Вот что говорит стандарт C:

Макрос errno не обязательно должен быть идентификатором объекта. Он может расширяться до изменяемой lvalue, вызванной вызовом функции (например, *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

Ответ 4

Во многих системах 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) */

Ответ 5

Я думаю, что ответ "это зависит". Резервные библиотеки времени выполнения C обычно реализуют errno как вызов функции (макрос расширяется до функции), если вы создаете поток кода с правильными флагами.

Ответ 6

Это от <sys/errno.h> на моем Mac:

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

Итак, errno теперь является функцией __error(). Эта функция реализована так, чтобы быть потокобезопасной.

Ответ 7

да, как объясняется в странице errno man, а остальные ответы, errno - это поток переменная.

Однако, есть глупая деталь, которую можно легко забыть. Программы должны сохранять и восстанавливать errno любого обработчика сигнала, выполняющего системный вызов. Это связано с тем, что сигнал будет обрабатываться одним из потоков процесса, который может перезаписать его значение.

Поэтому обработчики сигналов должны сохранять и восстанавливать errno. Что-то вроде:

void sig_alarm(int signo)
{
 int errno_save;

 errno_save = errno;

 //whatever with a system call

 errno = errno_save;
}