errno 是线程安全的吗?

2024-10-10 09:28:00
admin
原创
221
摘要:问题描述:在 中errno.h,此变量被声明为extern int errno;,所以我的问题是,在多线程代码中,在一些调用之后检查值或使用 perror() 是否安全errno。这是一个线程安全变量吗?如果不是,那么有什么替代方案?我在 x86 架构上使用带有 gcc 的 linux。解决方案 1:是的,它...

问题描述:

在 中errno.h,此变量被声明为extern int errno;,所以我的问题是,在多线程代码中,在一些调用之后检查值或使用 perror() 是否安全errno。这是一个线程安全变量吗?如果不是,那么有什么替代方案?

我在 x86 架构上使用带有 gcc 的 linux。


解决方案 1:

是的,它是线程安全的。在 Linux 上,全局 errno 变量是线程特定的。POSIX 要求 errno 是线程安全的。

请参阅http://www.unix.org/whitepapers/reentrant.html

在 POSIX.1 中,errno 被定义为外部全局变量。但这种定义在多线程环境中是不可接受的,因为使用 errno 可能会导致不确定的结果。问题是两个或多个线程可能会遇到错误,所有错误都会导致设置相同的 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 是线程局部的;在一个线程中设置它不会影响任何其他线程中的值。

解决方案 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 手册页和其他回复中所解释的那样,errno 是一个线程局部变量。

然而,有一个细节很容易被遗忘。程序应该在执行系统调用的任何信号处理程序上保存和恢复 errno。这是因为该信号将由可能覆盖其值的进程线程之一处理。

因此,信号处理程序应该保存并恢复 errno。例如:

void sig_alarm(int signo)
{
 int errno_save;

 errno_save = errno;

 //whatever with a system call

 errno = errno_save;
}

解决方案 4:

我们可以通过在机器上运行一个简单的程序来检查。

#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
", 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 

请注意,所有线程的地址都是不同的。

解决方案 5:

在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

解决方案 6:

这是<sys/errno.h>在我的 Mac 上拍摄的:

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

所以errno现在是一个函数__error()。该函数的实现是线程安全的。

解决方案 7:

在许多 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) */

解决方案 8:

我认为答案是“视情况而定”。如果您使用正确的标志构建线程代码,线程安全 C 运行时库通常会将 errno 实现为函数调用(宏扩展为函数)。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2098  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1460  
  建筑行业正处于数字化转型的关键时期,建筑产品生命周期管理(PLM)系统的实施对于提升项目效率、质量和协同性至关重要。特别是在 2025 年,基于建筑信息模型(BIM)的项目进度优化工具成为众多建筑企业关注的焦点。这些工具不仅能够整合项目全生命周期的数据,还能通过精准的分析和模拟,为项目进度管理提供强大支持。BIM 与建...
plm是什么软件   13  
  PLM系统开发的重要性与现状PLM(产品生命周期管理)系统在现代企业的产品研发、生产与管理过程中扮演着至关重要的角色。它贯穿产品从概念设计到退役的整个生命周期,整合了产品数据、流程以及人员等多方面的资源,极大地提高了企业的协同效率和创新能力。通过PLM系统,企业能够实现产品信息的集中管理与共享,不同部门之间可以实时获取...
国产plm软件   15  
  PLM(产品生命周期管理)系统在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和技术的飞速发展,企业对PLM系统的迭代周期优化需求日益迫切。2025年敏捷认证对项目管理提出了新的要求,其中燃尽图作为一种强大的可视化工具,在PLM系统迭代周期优化中有着广泛且重要的应用。深入探讨这些应用,对于提升企业的项...
plm系统主要干什么的   16  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用