На первый взгляд
errno выглядит как обычная глобальная переменная:
if (errno == EINTR) {
// retry
}Но если 32 потока одновременно выполняют системные вызовы, почему они не перезаписывают errno друг у друга?
Потому что errno на самом деле не обычная глобальная переменная.
В glibc это макрос, который обращается к специальной функции __errno_location():
extern int *__errno_location(void);
#define errno (*__errno_location())
Функция возвращает указатель на отдельную область памяти для каждого потока через механизм Thread-Local Storage (TLS).
Получается, каждый поток работает со своим экземпляром errno:
- Поток №1 читает и изменяет собственное значение.
- Поток №2 работает со своим.
- Поток №32 получает отдельное значение.
Поэтому потоки не перезаписывают errno друг у друга.
Это не особенность только Linux: POSIX гарантирует потокоспецифичное поведение errno.
Ещё один нюанс: проверять errno нужно только после того, как функция сообщила об ошибке. Само по себе изменение errno не означает, что операция завершилась неудачно.