线程特定数据,也被称为线程私有数据,是一种存储和查找一个特定线程相关数据的机制。我们称这个数据为线程特定或线程私有的原因,是因为每个线程访问它自己独立的数据拷贝,而不用担心和其它线程的访问的同步。
线程特定数据看似很复杂,其实我们可以把它理解为就是一个索引和指针。key结构中存储的是索引,pthread结构中存储的是指针,指向线程中的私有数据,通常是malloc函数返回的指针。
POSIX要求实现POSIX的系统为每个进程维护一个称之为Key的结构数组(如图1所示),这个数组中的每个结构称之为一个线程特定数据元素。POSIX规定系统实现的Key结构数组必须包含不少于128个线程特定元素,而每个线程特定数据元素至少包含两项内容:使用标志和析构函数指针。key结构中的标志指示这个数组元素是否使用,所有的标志初始化为“不在使用”。
当一个线程调用pthread_key_create创建一个新的线程特定数据元素时,系统搜索其所在进程的Key结构数组,找出其中第一个未使用的元素,并通过keyptr返回该元素的键,即就是我们前面说的索引。pthread_key_create函数的第二个参数destructor是一个函数指针,指向一个析构函数,用于线程结束以后的一些后期后期处理工作,析构函数的额参数就是线程特定数据的指针。
除了进程范围内地的key结构数组外,系统还在进程中维护关于每个线程的线程结构,把这个特定于线程的结构称为pthread结构,它的部分内容是于key数组对应的指针数组(如图2所示),它的128个指针和进程中的128个可能的键(索引)是逐一关联的。指针指向的内存就是线程特有数据。
下面看一个具体的过程,启动一个进程并创建了若干线程,其中一个线程(比如线程1),要申请线程私有数据,系统调用pthread_key_creat()在图1所示的key结构数组中找到第一个未用的元素,并把它的键,也就是看面说的索引(0-127),返回给调用者,假设返回的索引是1,线程之后通过pthrea_getspecific()调用获得本线程的pkey[1]值,返回的是一个空指针ptr = null,这个指针就是我们可以通过索引1使用的线程数据的首地址了,但是他现在为空,因此根据实际情况用malloc分配一快内存,在使用pthread_setspecific()调用将特定数据的指针指向刚才分配到内存区域。整个过程结束后key结构和pthread结构如图3所示,
在操作线程特定数据时涉及到的函数调用有一下几个:
#include <pthread.h> //成功返回0,失败返回错误号。 int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *)); //返回线程特定数据,或者如果没有值关联到这个关键字时返回NULL。 void *pthread_getspecific(pthread_key_t key); //成功返回0,失败返回错误号。 int pthread_setspecific(pthread_key_t key, const void *value); pthread_once_t initflag = PTHREAD_ONCE_INIT; //成功返回0,失败返回错误码。 int pthread_once(pthread_once_t *initflag, void (*initfn)(void)); //成功返回0,失败返回错误号。 int pthread_key_delete(pthread_key_t *key);
线程特定数据的典型用法如下:
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); if( (ptr = pthread_getspecific(key)) == NULL ){ ptr = malloc(len); pthread_setspecific(key,ptr); ... } ... }
当调用pthread_key_create 后会产生一个所有线程都可见的线程特定数据(TSD)的键值(如上图中所有的线程都会得到一个pkey[1]的值), 但是这个键所指向的真实数据却是不同的,虽然都是pkey[1], 但是他们并不是指向同一块内存,而是指向了只属于自己的实际数据, 因此, 如果线程0更改了pkey[1]所指向的数据, 而并不能够影像到线程n;
在线程调用pthread_setspecific后会将每个线程的特定数据与thread_key_t绑定起来,虽然只有一个pthread_key_t,但每个线程的特定数据是独立的内存空间,当线程退出时会执行destructor 函数。
/** 示例1: 设置/获取线程特定数据 在两个线程中分别设置/获取线程特定数据, 查看两个线程中的数据是否是一样的(肯定是不一样的O(∩_∩)O~) **/ pthread_key_t key; typedef struct Tsd { pthread_t tid; char *str; } tsd_t; //用来销毁每个线程所指向的实际数据 void destructor_function(void *value) { free(value); cout << "destructor ..." << endl; } void *thread_routine(void *args) { //设置线程特定数据 tsd_t *value = (tsd_t *)malloc(sizeof(tsd_t)); value->tid = pthread_self(); value->str = (char *)args; pthread_setspecific(key, value); printf("%s setspecific, address: %p\n", (char *)args, value); //获取线程特定数据 value = (tsd_t *)pthread_getspecific(key); printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str); sleep(2); //再次获取线程特定数据 value = (tsd_t *)pthread_getspecific(key); printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str); pthread_exit(NULL); } int main() { //这样每个线程当中都会有一个key可用了, //但是每个key所绑定的实际区域需要每个线程自己指定 pthread_key_create(&key, destructor_function); pthread_t tid1, tid2; pthread_create(&tid1, NULL, thread_routine, (void *)"thread1"); pthread_create(&tid2, NULL, thread_routine, (void *)"thread2"); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_key_delete(key); return 0; }
/** 示例2:运用pthread_once, 让key只初始化一次 注意: 将对key的初始化放入到init_routine中 **/ pthread_key_t key; pthread_once_t once_control = PTHREAD_ONCE_INIT; typedef struct Tsd { pthread_t tid; char *str; } tsd_t; //线程特定数据销毁函数, //用来销毁每个线程所指向的实际数据 void destructor_function(void *value) { free(value); cout << "destructor ..." << endl; } //初始化函数, 将对key的初始化放入该函数中, //可以保证inti_routine函数只运行一次 void init_routine() { pthread_key_create(&key, destructor_function); cout << "init..." << endl; } void *thread_routine(void *args) { pthread_once(&once_control, init_routine); //设置线程特定数据 tsd_t *value = (tsd_t *)malloc(sizeof(tsd_t)); value->tid = pthread_self(); value->str = (char *)args; pthread_setspecific(key, value); printf("%s setspecific, address: %p\n", (char *)args, value); //获取线程特定数据 value = (tsd_t *)pthread_getspecific(key); printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str); sleep(2); //再次获取线程特定数据 value = (tsd_t *)pthread_getspecific(key); printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str); pthread_exit(NULL); } int main() { pthread_t tid1, tid2; pthread_create(&tid1, NULL, thread_routine, (void *)"thread1"); pthread_create(&tid2, NULL, thread_routine, (void *)"thread2"); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_key_delete(key); return 0; }
https://blog.csdn.net/zjf280441589/article/details/43883033
http://www.360doc.com/content/18/0617/11/56818466_763036051.shtml
原文地址:https://www.cnblogs.com/tianzeng/p/9192340.html