线程特定数据

    线程特定数据也称线程私有数据,是存储和查询某个特定线程相关数据的一种机制。在分配线程特定数据之前,需要创建与该数据相关联的键,以用于获取对线程特定数据的访问。使用函数 pthread_key_create 可创建一个键,而对所有的线程,都可以通过 pthread_key_delete 来取消键与线程特定数据值之间的关联关系。
#include <pthread.h>
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));
int pthread_key_delete(pthread_key_t key);
                    /* 两个函数返回值:若成功,返回 0;否则,返回错误编号 */

    创建的键存储在 keyp 指向的内存单元中,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程特定数据地址进行关联。创建新键时,每个线程的数据地址都设为空值。除了创建键以外,pthread_key_create 还可以为该键关联一个可选择的析构函数。当这个线程退出时,如果数据地址已经被置为非空值,那么析构函数就会被调用,它唯一的参数就是该数据地址。当线程调用 pthread_exit 或者返回时,析构函数就会被调用;而当线程取消时,只有在最后的清理处理程序返回后,析构函数才会被调用。线程通常使用 malloc 为线程特定数据分配内存,所以析构函数经常调用 free 函数来释放已分配的内存,以免产生内存泄露。线程可以为线程特定数据分配多个键,每个键都可以关联一个析构函数。线程退出时,线程特定数据的析构函数将按照操作系统实现中定义的顺序被调用。当所有的析构函数都调用完成后,系统会检查是否还有非空的线程特定数据值与键关联。如果有的话,会再次调用析构函数。该过程会一直重复直到线程所有的键都为空线程特定数据值,或者已经做了 PTHREAD_DESTRUCTOR_ITERATIONS 次尝试。
    调用 pthread_key_delete 并不会激活与键关联的析构函数,要释放任何与键关联的线程特定数据值的内存,需要在应用程序中采取额外的步骤。
    为确保分配的键不会因初始化阶段的竞争而发生变动,可以使用函数 pthread_once。键一旦创建以后,就可以通过 pthread_setspecific 函数把键和线程特定数据关联起来,也可通过 pthread_getspecific 函数获得线程特定数据的地址。
#include <pthread.h>
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));

int pthread_setspecific(pthread_key_t key, const void *value);
                    /* 两个函数返回值:若成功,返回 0;否则,返回错误编号 */
void *pthread_getspecific(pthread_key_t key);
                    /* 返回值:线程特定数据值;若没有值与该键关联,返回 NULL */

    这里的 initflag 必须是一个非本地变量(如全局变量或静态变量),而且必须初始化为 PTHREAD_ONCE_INIT。就算每个线程都调用 pthread_once,系统也能保证初始化例程 initfn 只被调用一次,这可以避免创建键时出现冲突。
    下面给出了 getenv 的一个假设实现,它使用线程特定数据来维护每个线程的数据缓冲区副本,用于存放各自的返回字符串。
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

static pthread_key_t	key;
static pthread_once_t	init_done = PTHREAD_ONCE_INIT;
static pthread_mutex_t	env_mutex = PTHREAD_MUTEX_INITIALIZER;

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

#define MAXSTRINGSZ	4096
extern char **environ;

char *getenv(const char *name){
	pthread_once(&init_done, key_init);
	pthread_mutex_lock(&env_mutex);
	char *env_buf = NULL;
	if((env_buf=(char *)pthread_getspecific(key)) == NULL){
		if((env_buf=malloc(MAXSTRINGSZ)) == NULL){
			pthread_mutex_unlock(&env_mutex);
			return NULL;
		}
		pthread_setspecific(key, env_buf);
	}
	int i = 0, len = strlen(name);
	for(; environ[i] != NULL; i++){
		if(strncmp(name, environ[i], len)==0 && environ[i][len]=='='){
			strncpy(env_buf, &environ[i][len+1], MAXSTRINGSZ-1);
			pthread_mutex_unlock(&env_mutex);
			return env_buf;
		}
	}
	pthread_mutex_unlock(&env_mutex);
	return NULL;
}

    这里使用 pthread_once 来确保只为将要使用的线程特定数据创建一个键。如果 pthread_getspecific 返回的是空指针,就需要先分配内存缓冲区,然后再把键与该内存缓冲区关联起来,否则就使用返回的内存缓冲区。对析构函数,使用 free 来释放之前由 malloc 分配的内存。只有当线程特定数据值为非空时,析构函数才会被调用。
    注意,相较于 线程重入中实现的 getenv_r 版本,虽然该版本的 genenv 也使用了互斥量来保证线程安全,但它并不是异步信号安全的。对信号处理程序而言,即使使用递归的互斥量,该版本也不可能是可重入的,因为它调用了本身就不是异步信号安全的 malloc 函数。

猜你喜欢

转载自aisxyz.iteye.com/blog/2401061