线程私有数据(thread_specific_data)详解
进程内的所有线程共享进程的数据空间,所以全局变量为所有线程共有。在某些场景下,线程需要保存自己的私有数据,这时可以创建线程私有数据(Thread-specific Data)TSD来解决。在线程内部,私有数据可以被线程的各个接口访问,但对其他线程屏蔽。
TSD采用了一种被称为一键多值的技术,即一个键对应多个数值。访问数据时都是通过键值来访问,好像是对一个变量进行访问,其实是在访问不同的数据。使用线程私有数据时,首先要为每个线程数据创建一个相关联的键。在各个线程内部,都使用这个公用的键来指代线程数据,但是在不同的线程中,这个键代表的数据是不同的。
POSIX中操作线程私有数据的主要通过以下4个接口来实现:
- pthread_key_create:创建一个键
1 int pthread_key_create(pthread_key_t *key, void (*destr_function) (void*));
首先从linux的TSD池中分配一项,然后将其值赋给key供以后访问使用。接口的第一个参数是指向键的指针,第二参数是函数指针,如果该指针不为空,那么在线程执行完毕退出时,以key指向的内容为参数调用destr_function(),释放分配的缓冲区以及其他数据。destr_function非常便利,即使线程执行过程中由于被cancel而非正常终止,该函数也会被调用,如果key中的值为NULL,该函数不会被调用,但是如果线程调用了exit系列函数或者abort或者其它非正常退出时,就不会调用析构函数。
key被创建之后,因为是全局变量,所以所有的线程都可以访问。各个线程可以根据需求向key中填入不同的值,这就相当于提供了一个同名而值不同的全局变量,即一键多值。一键多值依靠的是一个结构体数组,即
1 static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] ={{0,NULL}};
其中pthread_key_struct 的定义为:
1 struct pthread_key_struct 2 { 3 /* Sequence numbers. Even numbers indicated vacant entries. Note 4 that zero is even. We use uintptr_t to not require padding on 5 32- and 64-bit machines. On 64-bit machines it helps to avoid 6 wrapping, too. */ 7 uintptr_t seq; 8 9 /* Destructor for the data. */ 10 void (*destr) (void *); 11 };
PTHREAD_KEYS_MAX值为1024
创建一个TSD,相当于将结构体数组的某一个元素的seq值设置为为“in_use”,并将其索引返回给*key,然后设置destr_function()为destr()。pthread_key_create创建一个新的线程私有数据key时,系统会搜索其所在进程的key结构数组,找出一个未使用的元素,将其索引赋给*key。
- pthread_setspecific:为指定键值设置线程私有数据
1 int pthread_setspecific(pthread_key_t key, const void *pointer);
该接口将指针pointer的值(指针值而非其指向的内容)与key相关联,用pthread_setspecific为一个键指定新的线程数据时,线程必须释放原有的数据用以回收空间。
- pthread_getspecific:从指定键读取线程的私有数据
1 void * pthread_getspecific(pthread_key_t key);
- pthread_key_delete:删除一个键
1 int pthread_key_delete(pthread_key_t key);
该接口用来删除一个键,键所占用的内存将被释放。需要注意的是,键占用的内存被释放,与该键关联的线程数据所占用的内存并不被释放。因此,线程数据的释放必须在释放键之前完成。
编程建议:最后不删除线程私有数据键!!!尤其当一些线程仍然持有该键的值时,就更不该释放该键!!!
如果创建一个线程私有数据键,必须保证pthread_key_create对于每个pthread_key_t变量仅仅被调用一次,因为如果一个键被创建两次,其实是在创建两个不同的键,第二个键将覆盖第一个键,第一个键以及任何线程可能为其关联的线程私有数据值将丢失。
一次性初始化:
在传统的顺序编程中,一次性初始化经常通过使用布尔变量来管理。控制变量被静态初始化为0,而任何依赖于初始化的代码都测试该变量。如果变量值仍然为0,则它能实行初始化,然后将变量置为1。以后检查的代码将跳过初始化。
但是在多线程程序设计中,事情就变的复杂的多。如果多个线程并发地执行初始化序列代码,可能有2个线程发现控制变量为0,并且都实行初始化,而该过程本该仅仅执行一次。
如果我们需要对一个POSIX变量静态的初始化,可使用的方法是用一个互斥量对该变量的初始化进行控制。但有时候我们需要对该变量进行动态初始化,pthread_once就会方便的多。
函数原形:
pthread_once_t once_control=PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *once_control,void(*init_routine)(void));
参数:
once_control 控制变量
init_routine 初始化函数
返回值:
若成功返回0,若失败返回错误编号。
类型为pthread_once_t的变量是一个控制变量。控制变量必须使用PTHREAD_ONCE_INIT宏静态地初始化。
pthread_once函数首先检查控制变量,判断是否已经完成初始化,如果完成就简单地返回;否则,pthread_once调用初始化函数,并且记录下初始化被完成。如果在一个线程初始化时,另外的线程调用pthread_once,则调用线程等待,直到那个线程完成初始化返回。
多线程下容易重复初始化的代码段:
1 void destructor(void *); 2 3 pthread_key_t key; 4 int flag; 5 6 int threadfun(void *arg) 7 { 8 if(!flag) 9 { 10 flag = 1; 11 pthread_key_create(&key, destructor); 12 } 13 }
使用pthread_once:
1 void destructor(void *); 2 3 pthread_once_t initonce = PTHREAD_ONCE_INIT; 4 pthread_key_t key; 5 6 void thread_once(void) 7 { 8 pthread_key_create(&key, destructor); 9 } 10 11 int threadfun(void *arg) 12 { 13 pthread_once(&initonce, thread_once); 14 ... 15 }
thread_specific_data使用的简单示例如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <pthread.h> 4 5 6 static pthread_key_t key; 7 8 pthread_once_t once = PTHREAD_ONCE_INIT; 9 10 void once_run (void) 11 { 12 int rtn = 0; 13 static int times = 1; 14 15 rtn = pthread_key_create(&key, NULL); 16 { 17 printf("key create OK, by thread:%lu\n", pthread_self()); 18 } 19 20 printf("The %d times run\n", times); 21 times++; 22 } 23 24 25 void* thread_func (void* arg) 26 { 27 pthread_t tid = pthread_self(); 28 29 printf("thread %lu start running!\n", tid); 30 31 pthread_once(&once, once_run); 32 33 pthread_setspecific(key, (void*)tid); 34 35 printf("thread id:%lu, key value:%lu\n", tid, (pthread_t)pthread_getspecific(key)); 36 37 printf("thread %lu quit!\n", tid); 38 39 return 0; 40 } 41 42 43 int main () 44 { 45 pthread_t tid1, tid2; 46 pthread_create(&tid1, NULL, thread_func, NULL); 47 pthread_create(&tid2, NULL, thread_func, NULL); 48 49 pthread_join(tid1, NULL); 50 pthread_join(tid2, NULL); 51 52 printf("main thread exit!\n"); 53 return 0; 54 }
运行结果如下:

浙公网安备 33010602011771号