线程特定数据TSD总结
2016-03-25 22:39
288 查看
一线程的本质
二线程模型的引入
三线程特定数据
四关键函数说明
五刨根问底啥原理
六私有数据使用示例
七参考文档
在维护每个线程的私有数据的时候,我们可能会想到分配一个保存线程数据的数组,用线程的ID作为数组的索引来实现访问。
1. 系统生成的线程ID不能保证是一个小而连续的整数
2. 用数组实现的时候容易出现越界读写的情况
鉴于这两个问题,我们可以借助线程的私有数据(TSD)来解决这个问题。
提示:进程中的所有线程都可以访问进程的整个地址空间。除了使用寄存器以外(一个线程真正拥有的唯一私有存储是处理器的寄存器),线程没有办法阻止其他线程访问它的数据,线程私有数据也不例外。虽然底层的实现部分并不能阻止这种访问能力,但管理线程私有数据的函数可以提高线程间的数据独立性。
返回值:若成功则返回0,否则返回错误编号
功能:创建的键存放在keyp指向的内存单元,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程私有数据地址进行关联。
说明:
创建一个线程私有数据键,必须保证对于每个
返回值:若成功则返回0,否则返回错误编号
说明:initflag必须是一个非本地变量(即全局变量或静态变量),而且必须初始化为PTHREAD_ONCE_INIT。
当线程调用
线程可以为线程私有数据分配多个键注2,每个键都可以有一个析构函数与它关联。各个键的析构函数可以互不相同,当然它们也可以使用相同的析构函数。
线程退出时,线程私有数据的析构函数将按照操作系统实现中定义的顺序被调用。析构函数可能会调用另一个函数,该函数可能会创建新的线程私有数据而且把这个数据与当前的键关联起来。当所有的析构函数都调用完成以后,系统会检查是否还有非null的线程私有数据值与键关联,如果有的话,再次调用析构函数。这个过程会一直重复直到线程所有的键都为null值线程私有数据,或者已经做了最大次数的尝试注3。
创建新键时,每个线程的数据地址设为NULL。
返回值:若成功则返回0,否则返回错误编号
功能:取消键与线程私有数据值之间的关联关系。
说明:
调用
系统内部为每个进程维护了两种数据,
1. pthread_key_struct结构的“标志”指示这个数据元素是否正在使用,当然在刚开始时所有的标志初始化为“不在使用”;
2. pthread结构体中有一部分内容是我们称之为pkey数组的一个128个元素的指针数组;
3. 在分配线程私有数据之前需要调用
运行结果
运行结果
http://blog.chinaunix.net/uid-8917757-id-2450452.html
http://www.3fwork.com/b902/001375MYM016745/
二线程模型的引入
三线程特定数据
四关键函数说明
五刨根问底啥原理
六私有数据使用示例
七参考文档
一、线程的本质
Linux线程又称轻量进程(LWP),也就说线程本质是用进程之间共享用户空间模拟实现的。二、线程模型的引入
线程模型引入是为了数据共享,为什么又引入线程私有数据?有时候想让基于进程的接口适应多线程环境,这时候就需要为每个线程维护一份私有数据了,最典型的就是errno了。在维护每个线程的私有数据的时候,我们可能会想到分配一个保存线程数据的数组,用线程的ID作为数组的索引来实现访问。
1. 系统生成的线程ID不能保证是一个小而连续的整数
2. 用数组实现的时候容易出现越界读写的情况
鉴于这两个问题,我们可以借助线程的私有数据(TSD)来解决这个问题。
三、线程特定数据
线程私有数据(Thread Specific Data),是存储和查询与某个线程相关的数据的一种机制。把这种数据称为线程私有数据或线程特定数据的原因是,希望每个线程可以独立地访问数据副本,从而不需要考虑多线程同步问题。提示:进程中的所有线程都可以访问进程的整个地址空间。除了使用寄存器以外(一个线程真正拥有的唯一私有存储是处理器的寄存器),线程没有办法阻止其他线程访问它的数据,线程私有数据也不例外。虽然底层的实现部分并不能阻止这种访问能力,但管理线程私有数据的函数可以提高线程间的数据独立性。
四、关键函数说明
int pthread_key_create(pthread_key_t *keyp,void (*destructor)(void *));
返回值:若成功则返回0,否则返回错误编号
功能:创建的键存放在keyp指向的内存单元,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程私有数据地址进行关联。
说明:
创建一个线程私有数据键,必须保证对于每个
Pthread_key_t变量仅仅被调用一次,因为如果一个键被创建两次,其实是在创建两个不同的键。第二个键将覆盖第一个键,第一个键以及任何线程可能与其关联的线程私有数据值将丢失。解决这种竞争的办法是使用
pthread_once。
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));
返回值:若成功则返回0,否则返回错误编号
说明:initflag必须是一个非本地变量(即全局变量或静态变量),而且必须初始化为PTHREAD_ONCE_INIT。
当线程调用
pthread_exit或者线程执行返回,正常退出时,如果私有数据不为空且注册了析构函数,析构函数就会被调用,但如果线程调用了
exit、
_exit、
_Exit、
abort或出现其他非正常的退出时就不会调用析构函数注1 。
线程可以为线程私有数据分配多个键注2,每个键都可以有一个析构函数与它关联。各个键的析构函数可以互不相同,当然它们也可以使用相同的析构函数。
线程退出时,线程私有数据的析构函数将按照操作系统实现中定义的顺序被调用。析构函数可能会调用另一个函数,该函数可能会创建新的线程私有数据而且把这个数据与当前的键关联起来。当所有的析构函数都调用完成以后,系统会检查是否还有非null的线程私有数据值与键关联,如果有的话,再次调用析构函数。这个过程会一直重复直到线程所有的键都为null值线程私有数据,或者已经做了最大次数的尝试注3。
创建新键时,每个线程的数据地址设为NULL。
int pthread_key_delete(pthread_key_t *keyp);
返回值:若成功则返回0,否则返回错误编号
功能:取消键与线程私有数据值之间的关联关系。
说明:
调用
pthread_delete不会激活与键关联的析构函数,容易造成内存泄露。要释放任何与键对应的线程私有数据值的内存空间,需要在应用程序中采取额外的步骤。当删除线程私有数据键的时候,不会影响任何线程对该键设置的线程私有数据值,甚至不影响调用线程当前键值。建议最后才删除线程私有数据键,尤其当一些线程仍然持有该键的值时,就更不该释放该键。使用已经删除的私有数据键将导致未定义的行为。
五、刨根问底啥原理
线程私有数据实现的主要思想,如图所示系统内部为每个进程维护了两种数据,
pthread_key_struct结构体数组和
pthread结构体:
1. pthread_key_struct结构的“标志”指示这个数据元素是否正在使用,当然在刚开始时所有的标志初始化为“不在使用”;
2. pthread结构体中有一部分内容是我们称之为pkey数组的一个128个元素的指针数组;
3. 在分配线程私有数据之前需要调用
pthread_key_create创建与该数据相关联的健。系统搜索
pthread_key_struct结构数组,找出第一个“不在使用”的元素,并把该元素的索引(0~127)称为“键”,返回给调用线程的正是这个索引。这个键可以被进程中的所有线程使用,每个线程把这个键与不同的线程私有数据地址进行关联(个人愚见:此处的关联指的就是使用相同的索引)。虽然索引值相同,但是由于各个线程pkey数组的起始地址不同,结果导致每个线程根据相同的索引取得的值不同(该值就是存放的私有数据)。
六、私有数据使用示例
/* * TSD(Thread Specific Data)使用步骤说明 * 运行环境:SlackwareLinux 64bit */ #include <stdio.h> #include <pthread.h> //1、创建一个类型为 pthread_key_t 类型的变量 pthread_key_t key; void destructor(void *arg) { //arg即为保存的线程数据 printf("destructor executed in thread %lx, param = %lx\n", pthread_self(), arg); } void * child1(void *arg) { pthread_t tid = pthread_self(); printf("thread1 %lx entering\n", tid); //3、进行线程数据存储。 //param1为前面声明的 pthread_key_t key, //param2为要存储的数据, void*类型说明可以存储任何类型的数据 pthread_setspecific(key, (void *)tid); sleep(2); //让出cpu //4、取出所存储的线程数据。 //param为前面提到的 pthread_key_t key //如果没有线程私有数据值与键关联,pthread_getspecific将返回一个空指针,可以据此来确定是否需要调用pthread_setspecific。 printf("thread1 %lx returned %lx\n", tid, pthread_getspecific(key)); } void *child2(void *arg) { pthread_t tid = pthread_self(); printf("thread2 %lx entering\n", tid); pthread_setspecific(key, (void *)tid); sleep(1); printf("thread2 %lx returned %lx\n", tid, pthread_getspecific(key)); } int main(int argc, char *argv[]) { pthread_t tid1, tid2; printf("main thread %lx entering\n", pthread_self()); //2、把key与不同的线程私有数据地址进行关联 //第一个参数就是步骤1中声明的key的地址; //第二个参数是一个清理线程存储的函数,不是动态申请的该函数指针可以设成 NULL; pthread_key_create(&key, destructor); pthread_create(&tid1, NULL, child1, NULL); pthread_create(&tid2, NULL, child2, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); //5、解除key与线程私有数据地址的关联 pthread_key_delete(key); printf("main thread %lx returned\n", pthread_self()); return 0; }
运行结果
root@darkstar:/scratchbox/test/lidonghai# ./a.out main thread b77336c0 entering thread1 b7732b90 entering thread2 b6f32b90 entering thread2 b6f32b90 returned b6f32b90 destructor executed in thread b6f32b90, param = b6f32b90 thread1 b7732b90 returned b7732b90 destructor executed in thread b7732b90, param = b7732b90 main thread b77336c0 returned
/* * 三个线程:主线程,th1,th2各自有自己的私有数据区域 */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <pthread.h> static pthread_key_t str_key; //创建一个类型为 pthread_key_t 类型的变量 static pthread_once_t str_alloc_key_once = PTHREAD_ONCE_INIT; //define a static variable that only be allocated once static void str_alloc_key(); static void str_alloc_destroy_accu(void* accu); char* str_accumulate(const char* s) { char* accu; pthread_once(&str_alloc_key_once,str_alloc_key); //str_alloc_key()这个函数只调用一次 accu = (char*)pthread_getspecific(str_key); //取得该线程对应的关键字所关联的私有数据空间首址 if(accu==NULL){ //每个新刚创建的线程这个值一定是NULL(没有指向任何已分配的数据空间) accu=malloc(1024); if(!accu) return NULL; accu[0] = 0; pthread_setspecific(str_key,(void*)accu);//设置该线程对应的关键字关联的私有数据空间 printf("Thread %lx: allocating buffer at %p\n",pthread_self(),accu); } strcat(accu,s); return accu; } static void str_alloc_key() { pthread_key_create(&str_key,str_alloc_destroy_accu);//创建关键字及其对应的内存释放函数,当进程创建关键字后,这个关键字是NULL。 printf("Thread %lx: allocated key %d\n",pthread_self(),str_key); } static void str_alloc_destroy_accu(void* accu) { printf("Thread %lx: freeing buffer at %p\n",pthread_self(),accu); free(accu); } //线程入口函数 void* process(void *arg) { char* res; res=str_accumulate("Result of "); if(strcmp((char*)arg,"first")==0) sleep(3); res=str_accumulate((char*)arg); res=str_accumulate(" thread"); printf("Thread %lx: \"%s\"\n",pthread_self(),res); return NULL; } //主线程函数 int main(int argc,char* argv[]) { char* res; pthread_t th1,th2; res=str_accumulate("Result of "); pthread_create(&th1,NULL,process,(void*)"first"); pthread_create(&th2,NULL,process,(void*)"second"); res=str_accumulate("initial thread"); printf("Thread %lx: \"%s\"\n",pthread_self(),res); pthread_join(th1,NULL); pthread_join(th2,NULL); pthread_exit(0); }
运行结果
[root@10h57 c]# ./pthread_private_data Thread b7fdd6c0 : allocated key 0 Thread b7fdd6c0: allocating buffer at 0x911c008 Thread b7fdd6c0: "Result of initial thread" Thread b7fdcb90: allocating buffer at 0x911c938 Thread b75dbb90: allocating buffer at 0x911cd40 Thread b75dbb90: "Resule of second thread" Thread b75dbb90: freeing buffer at 0x911cd40 Thread b7fdcb90: "Resule of first thread" Thread b7fdcb90: freeing buffer at 0x911c938 Thread b7fdd6c0: freeing buffer at 0x911c008
七、参考文档
http://blog.csdn.net/caigen1988/article/details/7901248http://blog.chinaunix.net/uid-8917757-id-2450452.html
http://www.3fwork.com/b902/001375MYM016745/
相关文章推荐
- Python3写爬虫(四)多线程实现数据爬取
- C#实现多线程的同步方法实例分析
- 浅谈chuck-lua中的多线程
- C#简单多线程同步和优先权用法实例
- C#多线程学习之(四)使用线程池进行多线程的自动管理
- C#多线程编程中的锁系统(三)
- 解析C#多线程编程中异步多线程的实现及线程池的使用
- C#多线程学习之(六)互斥对象用法实例
- 基于一个应用程序多线程误用的分析详解
- C#多线程学习之(三)生产者和消费者用法分析
- C#多线程学习之(一)多线程的相关概念分析
- C#多线程之Thread中Thread.IsAlive属性用法分析
- 分享我在工作中遇到的多线程下导致RCW无法释放的问题
- C#多线程编程之使用ReaderWriterLock类实现多用户读与单用户写同步的方法
- C#控制台下测试多线程的方法
- 21天学习android开发教程之SurfaceView与多线程的混搭
- Ruby 多线程的潜力和弱点分析
- C#中WPF使用多线程调用窗体组件的方法
- C#如何对多线程、多任务管理(demo)
- C#实现多线程的Web代理服务器实例