进程内所有的线程共享地址空间,任何声明为静态或外部的变量,或在进程堆声明的变量都可以被进程内的所有线程读写。

  1. static,extern,或堆变量的值是上次线程改写的值
  2. 一个线程真正拥有的唯一私有存储时处理器寄存器。甚至栈地址也能被共享,寄存器和“私有”堆栈都不能代替非线程代码中使用的持久静态存储

  当线程需要有个私有变量时,首先决定所有线程时候共享相同的值,或线程是否该有他自己的值

  1. 如果共享变量,使用静态或外部数据,同步跨越多线程对共享数据进行存取
  2. 如果每个线程需要个私有变量,在每个线程的堆栈中分配内存并将值保存在那里,但是代码需要任何线程发现私有数据,私有数据允许每个线程有一份变量的拷贝,好像每个线程有一连串公共键值索引私有数据

  程序创建一个键,每个线程能独立的设定或得到自己的键值,键对于所有的线程是相同的,每个键值能将它独立的键值与共享键所联系,每个线程能将它独立的键值与共享键联系,每个线程能在任何时间为键改变他的私有值

  线程特定数据看似很复杂,其实我们可以把它理解为就是一个索引指针。key结构中存储的是索引,pthread结构中存储的是指针,指向线程中的私有数据,通常是malloc函数返回的指针。

   POSIX要求实现POSIX的系统为每个进程维护一个称之为Key的结构数组(如图1所示),这个数组中的每个结构称之为一个线程特定数据元素。POSIX规定系统实现的Key结构数组必须包含不少于128个线程特定元素,而每个线程特定数据元素至少包含两项内容:使用标志和析构函数指针。key结构中的标志指示这个数组元素是否使用,所有的标志初始化为“不在使用”(线程私有数据为空NULL对于pthread意味着一些特殊的东西,所以除非确实需要,否则不要将一个线程私有数据置空,不是赋空值而是不再使用)

  当一个线程调用pthread_key_create创建一个新的线程特定数据元素时,系统搜索其所在进程的Key结构数组,找出其中第一个未使用的元素,并通过keyptr返回该元素的键,即就是我们前面说的索引pthread_key_create函数的第二个参数destructor是一个函数指针,指向一个析构函数,用于线程结束以后的一些后期后期处理工作,析构函数的额参数就是线程特定数据的指针。

  除了进程范围内地的key结构数组外,系统还在进程中维护关于每个线程的线程结构,把这个特定于线程的结构称为pthread结构,它的部分内容是于key数组对应的指针数组(如图2所示),它的128个指针和进程中的128个可能的键(索引)是逐一关联的。指针指向的内存就是线程特有数据。

  1. #include <pthread.h>
  2.  
  3. pthread_key_t keyp;
  4. //成功返回0,失败返回错误号。必须保证pthread_key_t只被创建一次,如果创建两次,第二个键覆盖第一个键
  5. //第一个键与任何线程相关设置的值一起丢失
  6. int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));
  7.  
  8. //成功返回0,失败返回错误号。
  9. int pthread_key_delete(pthread_key_t *key);
  10.  
  11. //返回线程特定数据,或者如果没有值关联到这个关键字时返回NULL。
  12. void *pthread_getspecific(pthread_key_t key);
  13.  
  14. //成功返回0,失败返回错误号
  15. int pthread_setspecific(pthread_key_t key, const void *value);
  1. pthread_key_create对于每个pthread_key_t只能被调用一次,如果创建两次,第二次将第一次的值覆盖
  2. pthreads保证一次只有128个线程私有数据键,由<limits.h>中的PTHREAD_KEYS_MAX指定
  3. 释放线程私有数据键时,不影响任何线程对该键设置的当前值,甚至不影响调用线程的当前键值,使用已删除的线程私有数据键导致未定义的行为
  4. 当没有线程持有该键的值时,才能删除线程私有数据键
  5. 不能把pthread_setspecific的value设置线程私有数据为NULL,因为此值意味着不是在赋空值,而是说该键在当前的线程不在有值
  6. 任何新建,初始值为NULL
  7. 当一个线程终止时,调用键的destructor函数前将对应的线程私有数据值清空,设置为NULL
  8. 如果线程的私有数据值存储的是地址,在destructor中释放时要传递给destructor参数,而非调用pthrad_getspecific的参数
  9. 终止线程对于某键的值为NULL的不调用destructor
  10. destructor仅在线程终止时被调用,而不是当线程私有数据键值改变时
  11. 线程私有数据的键的destructor函数在你替代现存的键值时不会被调用。即:如果在堆中分配一个结构并将它指向该结构的指针作为线程私有数据的值,则在以后分配一个新结构并且把它指向新结构的指针赋值给相同的线程私有数据键时,需要将旧结构释放。phtreads不会释放旧结构,也不会使用指向旧结构的指针调用你的destructor函数

destructor

  此函数是当线程退出时他处理有一些线程私有数据键定义的值。如果键值指向堆寄存器的指针,需要释放存储器避免每次线程终止时留下内存泄漏,当线程终止时,具有非空私有数据的键值的一个线程终止时,键的destructor将以当前值为参数被调用。

  当一个线程退出时,pthrads在进程中检查所有私有数据的键,并且将不是空的线程私有数据置空然后调用destructor函数

  如果从destructor函数中调用其他函数,destructor将为正在被破坏或其他任何键赋新值

  pthreads实现在核对列表某个固定的次数后再放弃,当放弃它时,最终的线程的私有数据值没有被破坏,如果值指向堆存储器的指针,结果可能是内存泄漏,<limits.h>中TPHREAD_DESTRUCTOR规定了系统检查的列表次数,并且值至少为4,或不停的检查,则总是设置线程私有数据值得destructor函数引起无限循环。

  通常仅当子系统1使用了取决于另外独立的子系统2的线程私有数据时,新的线程私有数据值才在destructor函数内被设置;如果子系统1的destructor需要调用子系统2,他可能无意中导致子系统2分配新的线程私有数据,尽管子系统2的destructor需要被再次调用以释放新数据,子系统1的线程私有数据仍保持NULL,所以循环将终止

  下面看一个具体的过程,启动一个进程并创建了若干线程,其中一个线程(比如线程1),要申请线程私有数据,系统调用pthread_key_creat()在图1所示的key结构数组中找到第一个未用的元素,并把它的键,也就是看面说的索引(0-127),返回给调用者,假设返回的索引是1,线程之后通过pthrea_getspecific()调用获得本线程的pkey[1]值,返回的是一个空指针ptr = null,这个指针就是我们可以通过索引1使用的线程数据的首地址了,但是他现在为空,因此根据实际情况用malloc分配一快内存,在使用pthread_setspecific()调用将特定数据的指针指向刚才分配到内存区域。整个过程结束后key结构和pthread

  1. void destructor(void *)
  2.  
  3. pthread_key_t key;
  4.  
  5. pthread_once_t init_done = PTHREAD_ONCE_INIT;
  6.  
  7. void thread_init(void)
  8. {
  9. err = pthread_key_create(&key, destructor);
  10. }
  11. int threadfunc(void *arg)
  12. {
  13. pthread_once(&init_done, thread_init); //保证只被创建一次
  14.  
  15. if( (ptr = pthread_getspecific(key)) == NULL ){
  16. ptr = malloc(len);
  17. pthread_setspecific(key,ptr);
  18. ...
  19. }
  20. ...
  21. }

  当调用pthread_key_create 后会产生一个所有线程都可见的线程特定数据(TSD)的键值(如上图中所有的线程都会得到一个pkey[1]的值), 但是这个键所指向的真实数据却是不同的,虽然都是pkey[1], 但是他们并不是指向同一块内存,而是指向了只属于自己的实际数据, 因此, 如果线程0更改了pkey[1]所指向的数据, 而并不能够影像到线程n;

在线程调用pthread_setspecific后会将每个线程的特定数据与thread_key_t绑定起来,虽然只有一个pthread_key_t,但每个线程的特定数据是独立的内存空间,当线程退出时会执行destructor 函数。

  1. /** 示例1: 设置/获取线程特定数据
  2. 在两个线程中分别设置/获取线程特定数据, 查看两个线程中的数据是否是一样的(肯定是不一样的O(∩_∩)O~)
  3. **/
  4. pthread_key_t key;
  5. typedef struct Tsd
  6. {
  7. pthread_t tid;
  8. char *str;
  9. } tsd_t;
  10. //用来销毁每个线程所指向的实际数据,线程私有数据键的destruct函数在你替代键值时不会被调用,
  11. void destructor_function(void *value)
  12. {
  13. free(value);
  14. cout << "destructor ..." << endl;
  15. }
  16.  
  17. void *thread_routine(void *args)
  18. {
  19. //设置线程特定数据
  20. tsd_t *value = (tsd_t *)malloc(sizeof(tsd_t));
  21. value->tid = pthread_self();
  22. value->str = (char *)args;
  23. pthread_setspecific(key, value);
  24. printf("%s setspecific, address: %p\n", (char *)args, value);
  25.  
  26. //获取线程特定数据
  27. value = (tsd_t *)pthread_getspecific(key);
  28. printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str);
  29. sleep();
  30.  
  31. //再次获取线程特定数据
  32. value = (tsd_t *)pthread_getspecific(key);
  33. printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str);
  34.  
  35. pthread_exit(NULL);
  36. }
  37.  
  38. int main()
  39. {
  40. //这样每个线程当中都会有一个key可用了,
  41. //但是每个key所绑定的实际区域需要每个线程自己指定
  42. pthread_key_create(&key, destructor_function);
  43.  
  44. pthread_t tid1, tid2;
  45. pthread_create(&tid1, NULL, thread_routine, (void *)"thread1");
  46. pthread_create(&tid2, NULL, thread_routine, (void *)"thread2");
  47.  
  48. pthread_join(tid1, NULL);
  49. pthread_join(tid2, NULL);
  50. pthread_key_delete(key);
  51.  
  52. ;
  53. }
  1. /** 示例2:运用pthread_once, 让key只初始化一次
  2. 注意: 将对key的初始化放入到init_routine中
  3. **/
  4. pthread_key_t key;
  5. pthread_once_t once_control = PTHREAD_ONCE_INIT;
  6. typedef struct Tsd
  7. {
  8. pthread_t tid;
  9. char *str;
  10. } tsd_t;
  11.  
  12. //线程特定数据销毁函数,
  13. //用来销毁每个线程所指向的实际数据
  14. void destructor_function(void *value)
  15. {
  16. free(value);
  17. cout << "destructor ..." << endl;
  18. }
  19.  
  20. //初始化函数, 将对key的初始化放入该函数中,
  21. //可以保证inti_routine函数只运行一次
  22. void init_routine()
  23. {
  24. pthread_key_create(&key, destructor_function);
  25. cout << "init..." << endl;
  26. }
  27.  
  28. void *thread_routine(void *args)
  29. {
  30. pthread_once(&once_control, init_routine);
  31.  
  32. //设置线程特定数据
  33. tsd_t *value = (tsd_t *)malloc(sizeof(tsd_t));
  34. value->tid = pthread_self();
  35. value->str = (char *)args;
  36. pthread_setspecific(key, value);
  37. printf("%s setspecific, address: %p\n", (char *)args, value);
  38.  
  39. //获取线程特定数据
  40. value = (tsd_t *)pthread_getspecific(key);
  41. printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str);
  42. sleep();
  43.  
  44. //再次获取线程特定数据
  45. value = (tsd_t *)pthread_getspecific(key);
  46. printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str);
  47.  
  48. pthread_exit(NULL);
  49. }
  50.  
  51. int main()
  52. {
  53. pthread_t tid1, tid2;
  54. pthread_create(&tid1, NULL, thread_routine, (void *)"thread1");
  55. pthread_create(&tid2, NULL, thread_routine, (void *)"thread2");
  56.  
  57. pthread_join(tid1, NULL);
  58. pthread_join(tid2, NULL);
  59. pthread_key_delete(key);
  60.  
  61. ;
  62. }

pthread线程私有数据的更多相关文章

  1. 【C/C++多线程编程之十】pthread线程私有数据

    多线程编程之线程私有数据      Pthread是 POSIX threads 的简称.是POSIX的线程标准.         线程同步从相互排斥量[C/C++多线程编程之六]pthread相互排 ...

  2. 线程私有数据和pthread_once

    #include <stdio.h> #include <pthread.h> pthread_key_t key; pthread_once_t ponce = PTHREA ...

  3. Posix线程编程指南(2) 线程私有数据

    概念及作用 在单线程程序中,我们经常要用到"全局变量"以实现多个函数间共享数据.在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有.但有时应用程序设计中有必要提供 ...

  4. [转] unix/linux下线程私有数据实现原理及使用方法

     在维护每个线程的私有数据的时候,我们可能会想到分配一个保存线程数据的数组,用线程的ID作为数组的索引来实现访问,但是有一个问题是系统生成的线程 ID不能保证是一个小而连续的整数,并且用数组实现的时候 ...

  5. UNIX环境高级编程——线程私有数据

    线程私有数据(Thread-specific data,TSD):存储和查询与某个线程相关数据的一种机制. 在进程内的所有线程都共享相同的地址空间,即意味着任何声明为静态或外部变量,或在进程堆声明的变 ...

  6. posix多线程--线程私有数据

    1.当多个线程共享一个变量时,将该变量定义为静态或外部变量,使用互斥量确保共享变量的安全访问.如果每个线程都需要一个私有变量值,则该值成为线程的私有数据.程序创建一个键,每个线程独立地设定或得到自己的 ...

  7. ZT linux 线程私有数据之 一键多值技术

    这个原作者的这个地方写错了 且他举的例子非常不好.最后有我的修正版本 pthread_setspecific(key, (void *)&my_errno); linux 线程私有数据之一键多 ...

  8. pthread_getspecific()--读线程私有数据|pthread_setspecific()--写线程私有数据

    原型: #include <pthread.h> void *pthread_getspecific(pthread_key_t key); int pthread_setspecific ...

  9. linux多线程学习笔记六--一次性初始化和线程私有数据【转】

    转自:http://blog.csdn.net/kkxgx/article/details/7513278 版权声明:本文为博主原创文章,未经博主允许不得转载. 一,一次性初始化 以保证线程在调用资源 ...

随机推荐

  1. springcloud13---zuul

    Zuul:API  GATEWAY (服务网关): http://blog.daocloud.io/microservices-2/ 一个客户端不同的功能请求不同的微服务,那么客户端要知道所有微服务的 ...

  2. 常用php操作redis命令整理(一)通用及字符串类型

    Key相关操作 TYPE 类型检测,字符串返回string,列表返回 list,set表返回set/zset,hash表返回hash,key不存在返回0 <?php echo $redis-&g ...

  3. linux及安全第八周总结——20135227黄晓妍

    实验部分 实验环境搭建 -rm menu -rf git clone https://github.com/megnning/menu.git cd menu make rootfs qemu -ke ...

  4. Web漏洞挖掘之网络信息探测

    我们在搜集目标系统信息的时候主要需要搜集的是:目标服务器系统信息(IP,服务器所用系统等):目标网站子域名:目标网站(服务器)的开放端口:目标域名信息.目标网站内容管理系统(CMS)等. 一.子域名搜 ...

  5. Ubuntu16.04下安装tensorflow(GPU加速)【转】

    本文转载自:https://blog.csdn.net/qq_30520759/article/details/78947034 版权声明:本文为博主原创文章,未经博主允许不得转载. https:// ...

  6. LeetCode——Next Permutation

    1. Question Implement next permutation, which rearranges numbers into the lexicographically next gre ...

  7. 寻找List之和的最近素数

    Task : Given a List [] of n integers , find minimum mumber to be inserted in a list, so that sum of ...

  8. GET 和 POST 方法的区别

    GET 和 POST 是 HTTP 请求的两种基本方法,最直观的区别就是 GET 把参数包含在 URL 中,POST 通过 request body 传递参数. 一些标准的区别: 1. GET 在浏览 ...

  9. POJ 1047 Round and Round We Go

    https://vjudge.net/problem/POJ-1047 题意: 给一个整数,它的长度为n,从1开始一直到n和该整数相乘,判断每次结果是否和原来的整数是循环的. 思路: 大整数的乘法. ...

  10. mongodb的安装与增删改查

    mongodb是一款分布式的文件存储的数据库,注意这两个词,分布式和文件存储.mongodb支持复制和分片,可以合理的运用空间的大小,也可以达到容灾的目的.另外文件存储也是一个特点,抛弃了传统的表的概 ...