线程私有数据(Thread-specific data,TSD):存储和查询与某个线程相关数据的一种机制。

  • 在进程内的所有线程都共享相同的地址空间,即意味着任何声明为静态或外部变量,或在进程堆声明的变量,都可以被进程内所有的线程读写。
  • 一个线程真正拥有的唯一私有存储是处理器寄存器,栈在“主人”故意暴露给其他线程时也是共享的
  • 有时需要提供线程私有数据:可以跨多个函数访问(全局);仅在某个线程有效(私有)(即在线程里面是全局)。例如:errno。

进程中的所有线程都可以访问进程的整个地址空间,除非使用寄存器(一个线程真正拥有的唯一私有存储是处理器寄存器),线程没有办法阻止其它线程访问它的数据,线程私有数据也不例外,但是管理线程私有数据的函数可以提高线程间的数据独立性。

进程内的所有线程共享进程的数据空间,因此全局变量为所有线程所共有。但有时线程也需要保存自己的私有数据,这时可以创建线程私有数据(Thread-specific Date)TSD来解决。在线程内部,私有数据可以被各个函数访问,但对其他线程是屏蔽的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量。(即在线程里面是全局变量

创建线程私有数据就是为了线程内部各个函数可以很容易的传递数据信息,因此要使线程外的函数不能访问这些数据,而线程内的函数使用这些数据就像线程内的全局变量一样,这些数据在一个线程内部是全局的,一般用线程私有数据的地址作为线程内各个函数访问该数据的入口。

线程私有数据采用了一种被称为一键多值的技术,即一个键对应多个数值。访问数据时都是通过键值来访问,好像是对一个变量进行访问,其实是在访问不同的数据。使用线程私有数据时,首先要为每个线程私有数据创建一个相关联的键。在各个线程内部,都使用这个公用的键来指代线程数据,但是在不同的线程中,这个键代表的数据是不同的。操作线程私有数据的函数主要有4个:pthread_key_create(创建一个键),pthread_setspecific(为一个键设置线程私有数据),pthread_getspecific(从一个键读取线程私有数据),pthread_key_delete(删除一个键)。

创建一个键:

  1. int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));//返回值:若成功则返回0,否则返回错误编号

在分配(malloc)线程私有数据之前,需要创建和线程私有数据相关联的键(key),这个键的功能是获得对线程私有数据的访问权。
     如果创建一个线程私有数据键,必须保证pthread_key_create对于每个Pthread_key_t变量仅仅被调用一次,因为如果一个键被创建两次,其实是在创建两个不同的键,第二个键将覆盖第一个键,第一个键以及任何线程可能为其关联的线程私有数据值将丢失。
     创建新键时,每个线程的私有数据地址设为NULL。

注意:创建的键存放在keyp指向的内存单元,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程私有数据地址进行关联。

除了创建键以外,pthread_key_create可以选择为该键关联析构函数,当线程退出时,如果线程私有数据地址被置为非NULL值,那么析构函数就会被调用。

注意:析构函数参数为退出线程的私有数据的地址如果私有数据的地址为NULL,就说明没有析构函数与键关联即不需要调用该析构函数。

当线程调用pthread_exit或者线程执行返回,正常退出时,析构函数就会被调用,但是如果线程调用了exit、_exit、Exit函数或者abort或者其它非正常退出时,就不会调用析构函数。

线程通常使用malloc为线程私有数据分配空间,析构函数通常释放已分配的线程私有数据的内存

线程可以为线程私有数据分配多个键,每个键都可以有一个析构函数与它关联。各个键的析构函数可以互不相同,当然它们也可以使用相同的析构函数。

线程退出时,线程私有数据的析构函数将按照操作系统实现定义的顺序被调用。析构函数可能调用另外一个函数,而该函数可能创建新的线程私有数据而且把这个线程私有数据和当前的键关联起来。当所有的析构函数都调用完成以后,系统会检查是否有非NULL的线程私有数据值与键关联,如果有的话,再次调用析构函数,这个过程一直重复到线程所有的键都为NULL值线程私有数据,或者已经做了PTHREAD_DESTRUCTOR_ITERATIONS中定义的最大次数的尝试。

取消键与线程私有数据之间的关联:

  1. int pthread_delete(pthread_key_t *keyp);//返回值:若成功则返回0,否则返回错误编号

注意调用pthread_delete不会激活与键关联的析构函数。删除线程私有数据键的时候,不会影响任何线程对该键设置的线程私有数据值,甚至不影响调用线程当前键值,所以容易造成内存泄露(因为键不与私有数据关联了,当线程正常退出的时候不会调用键的析构函数,最终导致线程的私有数据这块内存没有释放)。使用已经删除的私有数据键将导致未定义的行为。

注意:对于每个pthread_key_t变量(即键)必须仅调用一次pthread_key_create。如果一个键创建两次,其实是在创建不同的键,第二个键将覆盖第一个,第一个键与任何线程可能为其设置的值将一起永远的丢失。所以,pthread_key_create放在主函数中执行;或每个线程使用pthread_once来创建键。

线程私有数据与键关联:

  1. int pthread_setspecific(pthread_key_t key,const void *value);//返回值:若成功则返回0,否则返回错误编号
  2. void* pthread_getspecific(pthread_key_t key);//返回值:线程私有数据地址;若没有值与键关联则返回NULL

如果没有线程私有数据值与键关联,pthread_getspecific键返回NULL,可以依据此来确定是否调用pthread_setspecific。

注意:两个线程对自己的私有数据操作是互相不影响的。也就是说,虽然 key 是同名且全局,但访问的内存空间并不是相同的一个。key 就像是一个数据管理员,线程的私有数据只是到他那去注册,让它知道你这个数据的存在。

示例代码:

  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <stdlib.h>
  4.  
  5. typedef struct private_tag {
  6. pthread_t thread_id;
  7. char *string;
  8. } private_t;
  9.  
  10. pthread_key_t identity_key; /* Thread-specific data key */
  11. pthread_mutex_t identity_key_mutex = PTHREAD_MUTEX_INITIALIZER;
  12. long identity_key_counter = 0;
  13.  
  14. void identity_key_destructor (void *value)
  15. {
  16. private_t *private = (private_t*)value;
  17. int status;
  18.  
  19. printf ("thread \"%s\" exiting...\n", private->string);
  20. free (value);
  21. status = pthread_mutex_lock (&identity_key_mutex);
  22. if (status != 0)
  23. perror("pthread_mutex_lock");
  24. identity_key_counter--;
  25. if (identity_key_counter <= 0) {
  26. status = pthread_key_delete (identity_key);
  27. if (status != 0)
  28. perror("pthread_key_delete");
  29. printf ("key deleted...\n");
  30. }
  31. status = pthread_mutex_unlock (&identity_key_mutex);
  32. if (status != 0)
  33. perror("pthread_mutex_unlock");
  34. }
  35.  
  36. void *identity_key_get (void)
  37. {
  38. void *value;
  39. int status;
  40.  
  41. value = pthread_getspecific (identity_key);
  42. if (value == NULL) {
  43. value = malloc (sizeof (private_t));
  44. if (value == NULL)
  45. perror ("malloc");
  46. status = pthread_setspecific (identity_key, (void*)value);
  47. if (status != 0)
  48. perror("pthread_setspecific");
  49. }
  50. return value;
  51. }
  52.  
  53. void *thread_routine (void *arg)
  54. {
  55. private_t *value;
  56.  
  57. value = (private_t*)identity_key_get ();
  58. value->thread_id = pthread_self ();
  59. value->string = (char*)arg;
  60. printf ("thread \"%s\" starting...\n", value->string);
  61. sleep (2);
  62. return NULL;
  63. }
  64.  
  65. void main (int argc, char *argv[])
  66. {
  67. pthread_t thread_1, thread_2;
  68. private_t *value;
  69. int status;
  70.  
  71. status = pthread_key_create (&identity_key, identity_key_destructor);
  72. if (status != 0)
  73. perror("pthread_key_create");
  74. identity_key_counter = 3;
  75. value = (private_t*)identity_key_get ();
  76. value->thread_id = pthread_self ();
  77. value->string = "Main thread";
  78. status = pthread_create (&thread_1, NULL,thread_routine, "Thread 1");
  79. if (status != 0)
  80. perror("pthread_create");
  81. status = pthread_create (&thread_2, NULL,thread_routine, "Thread 2");
  82. if (status != 0)
  83. perror("pthread_create");
  84. pthread_exit (NULL);
  85. }

运行结果:

  1. huangcheng@ubuntu:~$ ./a.out
  2. thread "Main thread" exiting...
  3. thread "Thread 2" starting...
  4. thread "Thread 1" starting...
  5. thread "Thread 2" exiting...
  6. thread "Thread 1" exiting...
  7. key deleted...
  8. huangcheng@ubuntu:~$

示例代码2:

  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <stdlib.h>
  4.  
  5. typedef struct tsd_tag{
  6. pthread_t thread_id;
  7. char *string;
  8. }tsd_t;
  9.  
  10. pthread_key_t key;
  11. pthread_once_t once = PTHREAD_ONCE_INIT;
  12.  
  13. void once_routine(void)
  14. {
  15. int status;
  16.  
  17. printf("Initializing key\n");
  18. status = pthread_key_create(&key, NULL);
  19. if(status != 0){
  20. perror("pthread_key_create");
  21. }
  22. }
  23.  
  24. void *thread_routine(void *arg)
  25. {
  26. int status;
  27. tsd_t *value = NULL;
  28.  
  29. status = pthread_once(&once, once_routine);
  30. if(status != 0){
  31. perror("pthread_once");
  32. }
  33.  
  34. value = (tsd_t *)malloc(sizeof(tsd_t));
  35. if(value == NULL){
  36. perror("malloc");
  37. }
  38.  
  39. status = pthread_setspecific(key, (void *)value);
  40. if(status != 0){
  41. perror("pthread_setspecific");
  42. }
  43.  
  44. printf("%s set tsd value at %p\n", (char *)arg, value);
  45. value->thread_id = pthread_self();
  46. value->string = (char *)arg;
  47.  
  48. printf("%s starting......\n", (char *)arg);
  49. sleep(2);
  50. value = (tsd_t *)pthread_getspecific(key);
  51. if(value == NULL){
  52. printf("no thread-specific data value was associated \
  53. with key\n");
  54. pthread_exit(NULL);
  55. }
  56. printf("%s done......\n", value->string);
  57. }
  58.  
  59. int main(int argc, char **argv)
  60. {
  61. pthread_t thread1, thread2;
  62. int status;
  63.  
  64. status = pthread_create(&thread1, NULL, thread_routine, "thread 1");
  65. if(status != 0){
  66. perror("pthread_create");
  67. }
  68.  
  69. status = pthread_create(&thread2, NULL, thread_routine, "thread 2");
  70. if(status != 0){
  71. perror("pthread_create");
  72. }
  73.  
  74. pthread_exit(NULL);
  75. }

运行结果:

  1. huangcheng@ubuntu:~$ ./a.out
  2. Initializing key
  3. thread 2 set tsd value at 0x8fb7520
  4. thread 2 starting......
  5. thread 1 set tsd value at 0x8fb7530
  6. thread 1 starting......
  7. thread 2 done......
  8. thread 1 done......

示例代码3:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <pthread.h>
  4.  
  5. pthread_key_t key;
  6.  
  7. struct test_struct {
  8. int i;
  9. float k;
  10. };
  11.  
  12. void *child1 (void *arg)
  13. {
  14. struct test_struct struct_data;
  15.  
  16. struct_data.i = 10;
  17. struct_data.k = 3.1415;
  18.  
  19. pthread_setspecific (key, &struct_data);
  20. printf ("结构体struct_data的地址为 0x%p\n", &(struct_data));
  21. printf ("child1 中 pthread_getspecific(key)返回的指针为:0x%p\n", (struct test_struct *)pthread_getspecific(key));
  22.  
  23. printf ("利用 pthread_getspecific(key)打印 child1 线程中与key关联的结构体中成员值:\nstruct_data.i:%d\nstruct_data.k: %f\n", ((struct test_struct *)pthread_getspecific (key))->i, ((struct test_struct *)pthread_getspecific(key))->k);
  24.  
  25. printf ("------------------------------------------------------\n");
  26. }
  27.  
  28. void *child2 (void *arg)
  29. {
  30. int temp = 20;
  31. sleep (2);
  32. printf ("child2 中变量 temp 的地址为 0x%p\n", &temp);
  33. pthread_setspecific (key, &temp);
  34. printf ("child2 中 pthread_getspecific(key)返回的指针为:0x%p\n", (int *)pthread_getspecific(key));
  35. printf ("利用 pthread_getspecific(key)打印 child2 线程中与key关联的整型变量temp 值:%d\n", *((int *)pthread_getspecific(key)));
  36. }
  37.  
  38. int main (void)
  39. {
  40. pthread_t tid1, tid2;
  41.  
  42. pthread_key_create (&key, NULL);
  43.  
  44. pthread_create (&tid1, NULL, (void *)child1, NULL);
  45. pthread_create (&tid2, NULL, (void *)child2, NULL);
  46. pthread_join (tid1, NULL);
  47. pthread_join (tid2, NULL);
  48.  
  49. pthread_key_delete (key);
  50.  
  51. return (0);
  52. }

运行结果:

  1. huangcheng@ubuntu:~$ ./a.out
  2. 结构体struct_data的地址为 0x0xb77db388
  3. child1 pthread_getspecific(key)返回的指针为:0x0xb77db388
  4. 利用 pthread_getspecific(key)打印 child1 线程中与key关联的结构体中成员值:
  5. struct_data.i:10
  6. struct_data.k: 3.141500
  7. ------------------------------------------------------
  8. child2 中变量 temp 的地址为 0x0xb6fda38c
  9. child2 pthread_getspecific(key)返回的指针为:0x0xb6fda38c
  10. 利用 pthread_getspecific(key)打印 child2 线程中与key关联的整型变量temp 值:20

UNIX环境高级编程——线程私有数据的更多相关文章

  1. UNIX环境高级编程——线程属性

    pthread_attr_t 的缺省属性值 属性 值 结果 scope PTHREAD_SCOPE_PROCESS 新线程与进程中的其他线程发生竞争. detachstate PTHREAD_CREA ...

  2. UNIX环境高级编程——线程和fork

    当线程调用fork时,就为子进程创建了整个进程地址空间的副本.子进程通过继承整个地址空间的副本,也从父进程那里继承了所有互斥量.读写锁和条件变量的状态.如果父进程包含多个线程,子进程在fork返回以后 ...

  3. UNIX环境高级编程——线程

    线程包含了表示进程内执行环境必需的信息,其中包括进程中标示线程的线程ID.一组寄存器值.栈.调度优先级和策略.信号屏蔽字.errno变量以及线程私有数据. 进程的所有信息对该进程的所有线程都是共享的, ...

  4. UNIX环境高级编程——线程同步之互斥锁、读写锁和条件变量(小结)

    一.使用互斥锁 1.初始化互斥量 pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;//静态初始化互斥量 int pthread_mutex_init( ...

  5. UNIX环境高级编程——线程与进程区别

    进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性.进程和线程的区别在于: (1)一个程序至少有一个进程,一个进程至少有一个线程. (2)线程的划分尺度小于进 ...

  6. UNIX环境高级编程——线程同步之读写锁以及属性

    读写锁和互斥量(互斥锁)很类似,是另一种线程同步机制,但不属于POSIX标准,可以用来同步同一进程中的各个线程.当然如果一个读写锁存放在多个进程共享的某个内存区中,那么还可以用来进行进程间的同步, 互 ...

  7. Unix 环境高级编程---线程创建、同步、

    一下代码主要实现了linux下线程创建的基本方法,这些都是使用默认属性的.以后有机会再探讨自定义属性的情况.主要是为了练习三种基本的线程同步方法:互斥.读写锁以及条件变量. #include < ...

  8. UNIX环境高级编程——线程和信号

    每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的.这意味着尽管单个线程可以阻止某些信号,但当线程修改了与某个信号相关的处理行为以后,所有的线程都必须共享这个处理行为的改变.这样如果一 ...

  9. UNIX环境高级编程——线程属性之分离属性

    说到线程的分离状态,我认为,之所以会有这个状态,是因为系统对某些线程的终止状态根本不感兴趣导致的. 我们知道,进程中的线程可以调用: int pthread_join(pthread_t tid, v ...

随机推荐

  1. 关于InnoDB的读写锁类型以及加锁方式

    (本文为了方便,英文关键词都都采用小写方式,相关知识点会简单介绍,争取做到可以独立阅读) 文章开始我会先介绍本文需要的知识点如下: innodb的聚簇索引(聚集索引)和非聚簇索引(二级索引.非聚集索引 ...

  2. Beautiful Soup库

    原文传送门:静觅 » Python爬虫利器二之Beautiful Soup的用法

  3. ZhuSuan 是建立在Tensorflow上的贝叶斯深层学习的 python 库

    ZhuSuan 是建立在Tensorflow上的贝叶斯深层学习的 python 库. 与现有的主要针对监督任务设计的深度学习库不同,ZhuSuan 的特点是深入到贝叶斯推理中,从而支持各种生成模式:传 ...

  4. ArrayList源码和多线程安全问题分析

    1.ArrayList源码和多线程安全问题分析 在分析ArrayList线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证和分析. 1.1 数据结构 Arr ...

  5. CI下载与安装_基础配置_MVC

    CI:CodeIgniter -- 由Ellislab公司的CEORickEllis开发,是一个简单快速的PHP MVC框架. =============下载和安装================地址 ...

  6. JAVA 面试基础

    经典的Java基础面试题________________________________________________________________________________________ ...

  7. Node.js Smalloc

    稳定性: 1 - 试验 类: smalloc 由简单内存分配器(处理扩展原始内存的分配)支持的缓存.Smalloc 有以下函数: smalloc.alloc(length[, receiver][, ...

  8. 实验-使用VisualVM或JConsole进行对程序进行性能分析

    参考资料: 性能分析神器VisualVM java可视化监控工具 完成下列任务: 1.分析内存堆 使用+进行频繁的字符串拼接 2.CPU性能分析 3.线程分析 编程比较以下几个方法所创建的线程 Exe ...

  9. Android简易实战教程--第五十一话《使用Handler实现增加、减少、暂停计数》

    转载博客请注明出处:道龙的博客 之前,写过一篇使用异步任务AysncTask实现倒计时的小案例,喜欢的话可以参考博客:Android简易实战教程--第三十三话< AsyncTask异步倒计时&g ...

  10. Markdown对应Yelee主题语法

    概述 这里说的是Yelee主题的语法和原生语法是有些区别的:更多的基础语法可以到Cmd Markdown上面去查看:但是我觉得都会各有不同吧 注意这里说的不是真正意义上的Markdown语法 标题 一 ...