Chinaunix首页 | 论坛 | 博客
  • 博客访问: 41382
  • 博文数量: 10
  • 博客积分: 50
  • 博客等级: 民兵
  • 技术积分: 63
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-22 13:18
文章分类
文章存档

2012年(10)

我的朋友

分类:

2012-07-06 18:00:55

多线程中的errno

整理自:http://blog.csdn.net/summer_liuwei/article/details/6019846

整理自:http://hi.baidu.com/shenshuilanl/blog/item/82b01b872a64f92bc65cc31e.html

整理自:http://www.cnblogs.com/joeblackzqq/archive/2011/05/31/2065140.html

一、errno的由来
     在C编程中,errno是个不可缺少的变量,特别是在网络编程中。如果你没有用过errno,那只能说明你的程序不够健壮。当然,如果你是WIN32平台的GetLastError(),效果也是一样的。
     为什么会使用errno呢?个人认为,这是系统库设计中的一个无奈之举,他更多的是个技巧,而不是架构上的需要。我们观察下函数结构,可以发现,函数的参数返回值只有一个,这个返回值一般可以携带错误信息,比如负数表示错误,而正数表述正确的返回值,比如recv函数。但是对于一些返回指针的函数,如: char *get_str();这个方法显然没有用的。NULL可以表示发生错误,但是发生什么错误却毫无办法。于是,errno就诞生了。全局变量errno可以存放错误原因,当错误发生时,函数的返回值是可以通过非法值来提示错误的发生。

二、errno的线程安全
     errno是全局变量,但是在多线程环境下,就会变得很恐怖。当你调用一个函数时,发现这个函数发生了错误,但当你使用错误原因时,他却变成了另外一个线程的错误提示。想想就会觉得是件可怕的事情。
     将errno设置为线程局部变量是个不错的主意,事实上,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

通过上面的头文件内容看出来,在没有定义__LIBC或者定义_LIBC_REENTRANT的时候,errno是多线程/进程安全的。
一般而言, __ASSEMBLER__, _LIBC和_LIBC_REENTRANT都不会被编译器定义,但是如果我们定义_LIBC_REENTRANT一次又何妨那? 
为了检测一下你编译器是否定义上述变量,不妨使用下面一个简单程序。
#include 
#include 

int main( void )
{
#ifndef __ASSEMBLER__
        printf( "Undefine __ASSEMBLER__\n" );
#else
        printf( "define __ASSEMBLER__\n" );
#endif

#ifndef __LIBC
        printf( "Undefine __LIBC\n" );
#else
        printf( "define __LIBC\n" );
#endif

#ifndef _LIBC_REENTRANT
        printf( "Undefine _LIBC_REENTRANT\n" );
#else
        printf( "define _LIBC_REENTRANT\n" );
#endif

        return 0;
}

在UNIX的多线程下用到errno的时候,要留意下了,否则无法得到正确的errno。

1在AIX下,编译的时候需要加入-D_THREAD_SAFE

2在solaris下,编译的时候需要加入 -D_REENTRANT

3在linux下,不存在任何问题,可以放心大胆的使用

通过这个问题,还学到一招,查看include头文件,我想在某些时候会起到些作用。

比如,查看AIX下的/usr/include/errno.h文件,会发现这样的声明:

#if defined(_THREAD_SAFE) || defined(_THREAD_SAFE_ERRNO)
/*
* Per thread errno is provided by the threads provider. Both the extern int
* and the per thread value must be maintained by the threads library.
*/
extern   int      *_Errno( void );
#define errno    (*_Errno())

#else

extern int errno;

#endif   /* _THREAD_SAFE || _THREAD_SAFE_ERRNO */

所以不言而喻了,需要加入_THREAD_SAFE 或者 _THREAD_SAFE_ERRNO

然后查看solaris的/usr/include/errno.h就不是这两个宏了。


三、errno的实现

static pthread_key_t key;
static pthread_once_t key_once = PTHREAD_ONCE_INIT;
static void make_key()
{
    (void) pthread_key_create(&key, NULL);
}
int *_errno()
{
    int *ptr ;
    (void) pthread_once(&key_once, make_key);
    if ((ptr = pthread_getspecific(key)) == NULL) 
    {
        ptr = malloc(sizeof(int));        
        (void) pthread_setspecific(key, ptr);
    }
    return ptr ;
}

 四、errno的应用
    errno在库中得到广泛的应用,但是,错误编码实际上不止那么多。我们需要在自己的系统中增加更多的错误编码。一种方式就是直接利用errno,另外一种方式就是定义自己的user_errno。
   使用errno,strerror可能无法解析,这需要自己解决。但errno使用线程变量的方式值得借鉴。

阅读(633) | 评论(0) | 转发(0) |
0

上一篇:libevent 详解

下一篇:pthread_key_create用法

给主人留下些什么吧!~~