c++tricks——errno多线程安全

errno的由来

C编程中,errno是个不可缺少的变量,特别是在网络编程中。如果你没有用过errno,那只能说明你的程序不够健壮。当然,如果你是WIN32平台的GetLastError(),效果也是一样的。

为什么会使用errno呢?个人认为,这是系统库设计中的一个无奈之举,他更多的是个技巧,而不是架构上的需要。我们观察下函数结构,可以发现,函数的参数返回值只有一个,这个返回值一般可以携带错误信息,比如负数表示错误,而正数表述正确的返回值,比如recv函数。但是对于一些返回指针的函数,如:char *get_str();这个方法显然没有用的。NULL可以表示发生错误,但是发生什么错误却毫无办法。于是,errno就诞生了。全局变量errno可以存放错误原因,当错误发生时,函数的返回值是可以通过非法值来提示错误的发生。

errno的线程安全

errno是全局变量,但是在多线程环境下,就会变得很恐怖。当你调用一个函数时,发现这个函数发生了错误,但当你使用错误原因时,他却变成了另外一个线程的错误提示。想想就会觉得是件可怕的事情。

errno设置为线程局部thread local变量是个不错的主意,事实上,GCC中就是这么干的。他保证了线程之间的错误原因不会互相串改,当你在一个线程中串行执行一系列过程,那么得到的errno仍然是正确的。

看下,bits/errno.h的定义:

# ifndef __ASSEMBLER__

/* 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

#endif /* !__ASSEMBLER__ */

errno.h中是这样定义的:

/* Declare the `errno' variable, unless it's defined as a macro by

   bits/errno.h.  This is the case in GNU, where it is a per-thread

   variable.  This redeclaration using the macro still works, but it

   will be a function declaration without a prototype and may trigger

   a -Wstrict-prototypes warning.  */

#ifndef errno

extern int errno;

#endif

显然,errno实际上,并不是我们通常认为的是个整型数值,而是通过整型指针来获取值的。这个整型就是线程安全的。

errno的实现

static pthread_key_t key; //线程键,线程的私有数据

static pthread_once_t key_once = PTHREAD_ONCE_INIT; //控制变量,量必须使用PTHREAD_ONCE_INIT宏静态地初始化。

static void make_key()

{

    (void) pthread_key_create(&key, NULL); //创建线程键,第2个参数指定清理函数,即线程销毁时调用的内存释放函数。

}

int *_errno()

{

    int *ptr ;

    (void) pthread_once(&key_once, make_key);//函数pthread_once声明一个初始化函数(参数二)次调用pthread_once时执行函数,以后的调用将被它忽略。

    if ((ptr = pthread_getspecific(key)) == NULL) //获取线程的线程键

    {

        ptr = malloc(sizeof(int));        

        (void) pthread_setspecific(key, ptr);//线程绑定线程键

    }

    return ptr ;

}

其中pthread_once函数首先检查控制变量,判断是否已经完成初始化,如果完成就简单地返回;否则,pthread_once调用初始化函数,并且记录下初始化被完成。

errno的应用

errno在库中得到广泛的应用,但是,错误编码实际上不止那么多。我们需要在自己的系统中增加更多的错误编码。一种方式就是直接利用errno,另外一种方式就是定义自己的user_errno

使用errnostrerror可能无法解析,这需要自己解决。但errno使用线程变量的方式值得借鉴。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值