APUE 线程私有数据与errno的实现

线程私有数据

每个线程可以独立地访问数据副本,而不需要担心与其他线程的同步访问问题。

它提供了让基于进程的接口适应多线程环境的机制,一个很明显的实例就是errno。为了让线程也能够使用那些原本基于进程的系统调用和库例程,errno被重新定义为线程私有数据。这样,一个线程设置errno的操作并不会影响进程中其他线程的errno值。

 

#include<pthread.h>

int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));

“键”可以被进程中的所有线程使用,而每个线程把这个键与不同的线程私有数据地址进行关联,这就是其基本原理。

 

errno的定义

 

在<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

在<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实际上,并不是我们通常认为的是个整型数值,而是通过整型指针来获取值的。这个整型就是线程安全的。

另外,宏之所以这样实现,是因为标准库规定了必须能够通过&errno方式取得保存错误代码的变量的地址,因此__errno_location()函数的返回值是指针,并把宏定义为解引用函数返回的地址*__errno_location()。如果__errno_location直接返回int类型,此时就无法取得保存错误代码的变量的地址。

 

errno的可能实现

 

#include<stdlib.h>
#include<pthread.h>

static pthread_key_t key;
static pthread_once_t init_done = PTHREAD_ONCE_INIT;

static void
thread_init(void)
{
        pthread_key_create(&key, free);
}

int *
__errno_location_simply_fake(void)
{
        int *errp;

        /* 保证多线程环境里 key 只被创建一次 */
        pthread_once(&init_done, thread_init);

        errp = (int *)pthread_getspecific(key);
        if(errp == NULL) {
                errp = malloc(sizeof(int));
                if(errp != NULL)
                        pthread_setspecific(key, errp);
        }

        return errp;
}

 

参考:

1. 《APUE》第12.6节 线程私有数据

2. blog.csdn.net/romandion/archive/2008/01/11/2036975.aspx

3. bbs.chinaunix.net/viewthread.php