线程私有数据是存储和查询与某个线程相关的数据的一种机制。把这种数据称为线程私有/特定数据的原因是希望每个线程可以独立地访问数据副本而无需担心数据同步问题。
设计线程私有数据接口的原因是:(1).线程ID不能保证是小而连续的整数。例如上一章的程序threadid得到的结果:
main thread: pid 9508 tid 1611388704 (0x600bd720)
new thread: pid 9508 tid 1603331840 (0x5f90e700)
(2).它提供了基于进程的接口,又适应多线程环境的机制。一个明显的例子是errno(每个线程都有自己的副本)。
进程中的所有线程都可以访问进程的整个地址空间,除了使用寄存器以外,线程没有办法阻止其他线程访问它的数据,线程的私有数据也不例外。虽然底层实现并不能阻止这种访问能力,但管理线程私有数据的函数可以提高线程间的数据独立性。
(1).在分配线程私有数据之前,需要创建与该数据关联的键。这个键将用于获取对线程私有数据的访问权。使用pthread_key_create创建一个键:
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));
int pthread_key_delete(pthread_key_t *key);
创建的键存放在keyp指向的内存单元,这个键可以被进程中所有线程使用,但每个线程把这个键与不同的线程私有数据地址进行关联。创建新建时,每个线程的数据地址设为NULL。此外还可以为该键关联析构函数,仅正常退出时才调用,顺序和键创建顺序相同。如果线程调用了exit,_exit,_Exit,abort或者其他非正常退出时不会调用析构函数。通常线程通过使用malloc为线程私有数据分配内存,析构函数free已经释放的内存。pthread_key_delete用来取消键和线程的关联。
(2).在多线程并发条件下,如果每个线程都包含同一个键创建程序会引发竞争。确保键的创建只出现一次,使用pthread_once函数。
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));
initflag必须是一个全局或者静态变量,而且必须初始化为PTHREAD_ONCE_INIT。而void (*initfn)(void)是一个确保只能执行一次的函数。例如pthread_key_create:
- void destructor(void *);
-
- pthread_key_t key;
- pthread_once_t initdone = PTHREAD_ONCE_INIT;
-
- void
- thread_init(void)
- {
- err = pthread_key_create(&key, destructor);
- }
-
- int
- threadfunc(void *arg)
- {
- pthread_once(&init_done, thread_init);
- ......
- }
键一旦创建,就可以通过pthread_setspecific函数把键和线程私有数据关联出来,可以通过pthread_getspecific函数获得线程私有数据地址。
void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);
/*pthread_getspecific返回空则没有关联,可做判断*/
Textbook:
APUE