线程私有数据存在的原因在于有时需要维护基于每个线程的数据,通俗来说就是有些数据
在每个线程中都存在,各个线程都应该拥有自己的私有版本,改动自己的数据不应弄乱别人
的,假若这些数据是全局的,也就是说不能分配在线程栈中,那怎么办?数据的名称可是一样
的哦.如果简单地声明一个全局变量,则这个全局变量会被其他线程所共享.
从数据结构的观点看,这里的要求是每个线程都从同一个名称出发,但能访问到不同的数据
,这种类型的数据,很遗憾,不存在.只有通过操作,也就是函数去实现了.
想想我们能提供什么去获取这个数据吧:线程ID, 数据名称.我首先想到的是使用hash函数
,但处理冲突好像很麻烦,这大概就是pthread实现采用键并限制线程私有数据数量的原因
吧,细节就不管了.
具体操作线程私有数据的函数有:
int pthread_key_create(pthread_key_t *keyp,
void (* destructor )(void *));
每份私有数据对应着一个keyp,destructor用于线程退出时清理私有数据(明显,私有数据
不是分配在线程栈上,而是进程堆中).
int pthread_key_delete(pthread_key_t *key);
用于删除key,但好像没什么用.注意它不会调用destructor,所以要主动清理.
明显,每个线程对每一份私有数据拥有的key应该是一样的,所以不能让多个线程对同一个
key调用pthread_key_create().解决方法是使用pthread_once():
int pthread_once(pthread_once_t *initflag, void
(*initfn)(void));
initflag要求是非本地变量(例如全局,静态变量),并被初始化为PTHREAD_ONCE_INIT.
如果每个线程都调用pthread_once()以产生key,系统保证只在第一次调用pthread_once()
时调用initfn.
APUE建议使用下面的序列:
void destructor(void *);
pthread_key_t key;
pthread_once_t init_done = PTHREAD_ONCE_INIT;
void
thread_init(void)
{
err = pthread_key_create(&key, destructor);
}
int
threadfunc(void *arg)
{
pthread_once(&init_done, thread_init);
...
}
那为什么不直接在线程产生之前在主线程调用pthread_create_key()呢?原因是这会暴露
太多细节,试想私有数据是某个库函数的,主线程又如何得知它使用的key是什么呢?而如果
库函数要求主线程在生成其它线程之前就调用某个初始化函数,是不是太什么了一点?如果
说一个库函数调用还可以接受的话,那么大量的库函数调用呢?
当一个key产生之后,我们可以调用pthread_setspecific为这个key关线程私有数据,调用
pthread_getspecific去获取:
int pthread_setspecific(pthread_key_t key, const void *value);
void *pthread_getspecific(pthread_key_t key);
以下是APUE利用线程私有数据实现的线程安全getenv:
注意ARG_MAX不知道是在哪里定义的,自己定义一个就好,但一定要注意足够大,否则线程结束时free会出错.
以下是调用该函数的主函数,其中error.h在apue里提供的源码里可能也有吧,我是从unix网络编程(同一个作者)里拿来的: