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

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

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

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

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

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

创建一个键:

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中定义的最大次数的尝试。

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

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

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

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

线程私有数据与键关联:

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

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

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

示例代码:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h> typedef struct private_tag {
pthread_t thread_id;
char *string;
} private_t; pthread_key_t identity_key; /* Thread-specific data key */
pthread_mutex_t identity_key_mutex = PTHREAD_MUTEX_INITIALIZER;
long identity_key_counter = 0; void identity_key_destructor (void *value)
{
private_t *private = (private_t*)value;
int status; printf ("thread \"%s\" exiting...\n", private->string);
free (value);
status = pthread_mutex_lock (&identity_key_mutex);
if (status != 0)
perror("pthread_mutex_lock");
identity_key_counter--;
if (identity_key_counter <= 0) {
status = pthread_key_delete (identity_key);
if (status != 0)
perror("pthread_key_delete");
printf ("key deleted...\n");
}
status = pthread_mutex_unlock (&identity_key_mutex);
if (status != 0)
perror("pthread_mutex_unlock");
} void *identity_key_get (void)
{
void *value;
int status; value = pthread_getspecific (identity_key);
if (value == NULL) {
value = malloc (sizeof (private_t));
if (value == NULL)
perror ("malloc");
status = pthread_setspecific (identity_key, (void*)value);
if (status != 0)
perror("pthread_setspecific");
}
return value;
} void *thread_routine (void *arg)
{
private_t *value; value = (private_t*)identity_key_get ();
value->thread_id = pthread_self ();
value->string = (char*)arg;
printf ("thread \"%s\" starting...\n", value->string);
sleep (2);
return NULL;
} void main (int argc, char *argv[])
{
pthread_t thread_1, thread_2;
private_t *value;
int status; status = pthread_key_create (&identity_key, identity_key_destructor);
if (status != 0)
perror("pthread_key_create");
identity_key_counter = 3;
value = (private_t*)identity_key_get ();
value->thread_id = pthread_self ();
value->string = "Main thread";
status = pthread_create (&thread_1, NULL,thread_routine, "Thread 1");
if (status != 0)
perror("pthread_create");
status = pthread_create (&thread_2, NULL,thread_routine, "Thread 2");
if (status != 0)
perror("pthread_create");
pthread_exit (NULL);
}

运行结果:

huangcheng@ubuntu:~$ ./a.out
thread "Main thread" exiting...
thread "Thread 2" starting...
thread "Thread 1" starting...
thread "Thread 2" exiting...
thread "Thread 1" exiting...
key deleted...
huangcheng@ubuntu:~$

示例代码2:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h> typedef struct tsd_tag{
pthread_t thread_id;
char *string;
}tsd_t; pthread_key_t key;
pthread_once_t once = PTHREAD_ONCE_INIT; void once_routine(void)
{
int status; printf("Initializing key\n");
status = pthread_key_create(&key, NULL);
if(status != 0){
perror("pthread_key_create");
}
} void *thread_routine(void *arg)
{
int status;
tsd_t *value = NULL; status = pthread_once(&once, once_routine);
if(status != 0){
perror("pthread_once");
} value = (tsd_t *)malloc(sizeof(tsd_t));
if(value == NULL){
perror("malloc");
} status = pthread_setspecific(key, (void *)value);
if(status != 0){
perror("pthread_setspecific");
} printf("%s set tsd value at %p\n", (char *)arg, value);
value->thread_id = pthread_self();
value->string = (char *)arg; printf("%s starting......\n", (char *)arg);
sleep(2);
value = (tsd_t *)pthread_getspecific(key);
if(value == NULL){
printf("no thread-specific data value was associated \
with key\n");
pthread_exit(NULL);
}
printf("%s done......\n", value->string);
} int main(int argc, char **argv)
{
pthread_t thread1, thread2;
int status; status = pthread_create(&thread1, NULL, thread_routine, "thread 1");
if(status != 0){
perror("pthread_create");
} status = pthread_create(&thread2, NULL, thread_routine, "thread 2");
if(status != 0){
perror("pthread_create");
} pthread_exit(NULL);
}

运行结果:

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

示例代码3:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h> pthread_key_t key; struct test_struct {
int i;
float k;
}; void *child1 (void *arg)
{
struct test_struct struct_data; struct_data.i = 10;
struct_data.k = 3.1415; pthread_setspecific (key, &struct_data);
printf ("结构体struct_data的地址为 0x%p\n", &(struct_data));
printf ("child1 中 pthread_getspecific(key)返回的指针为:0x%p\n", (struct test_struct *)pthread_getspecific(key)); 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); printf ("------------------------------------------------------\n");
} void *child2 (void *arg)
{
int temp = 20;
sleep (2);
printf ("child2 中变量 temp 的地址为 0x%p\n", &temp);
pthread_setspecific (key, &temp);
printf ("child2 中 pthread_getspecific(key)返回的指针为:0x%p\n", (int *)pthread_getspecific(key));
printf ("利用 pthread_getspecific(key)打印 child2 线程中与key关联的整型变量temp 值:%d\n", *((int *)pthread_getspecific(key)));
} int main (void)
{
pthread_t tid1, tid2; pthread_key_create (&key, NULL); pthread_create (&tid1, NULL, (void *)child1, NULL);
pthread_create (&tid2, NULL, (void *)child2, NULL);
pthread_join (tid1, NULL);
pthread_join (tid2, NULL); pthread_key_delete (key); return (0);
}

运行结果:

huangcheng@ubuntu:~$ ./a.out
结构体struct_data的地址为 0x0xb77db388
child1 中 pthread_getspecific(key)返回的指针为:0x0xb77db388
利用 pthread_getspecific(key)打印 child1 线程中与key关联的结构体中成员值:
struct_data.i:10
struct_data.k: 3.141500
------------------------------------------------------
child2 中变量 temp 的地址为 0x0xb6fda38c
child2 中 pthread_getspecific(key)返回的指针为:0x0xb6fda38c
利用 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. 如何理解Spring IOC

    Spring IOC 思维导图 要了解控制反转( Inversion of Control ), 我觉得有必要先了解软件设计的一个重要思想:依赖倒置原则(Dependency Inversion Pr ...

  2. React学习笔记(一)- 环境搭建

    最近在学习react相关的知识,刚刚起步,一路遇坑不断.自己做个笔记,方便日后总结,也供相同趣味的小伙伴一起交流探讨. 学习时主要参考官网的教程:https://facebook.github.io/ ...

  3. 构建纯TypeScript应用

    构建纯TypeScript应用 现在只有命令行应用的例子. 前言 现在,应用开发的趋势是命令行接口应用和Web应用. node.js 和 typescript的崛起所以,这里讨论如何创建纯的TypeS ...

  4. tomcat7+jdk的keytool生成证书 配置https

    目前只会使用jdk的keytool来生成证书.本文仅介绍这种方法. 1Windows下: 1.1 生成keystore文件及导出证书 打开控制台: 运行: %JAVA_HOME%\bin\keytoo ...

  5. 63. Unique Paths II(中等, 能独立做出来的DP类第二个题^^)

    Follow up for "Unique Paths": Now consider if some obstacles are added to the grids. How m ...

  6. EXISTS的使用详解

    .exists的使用场合: exists 用于只能用于子查询,可以替代in,若匹配到结果,则退出内部 查询,并将条件标志为true,传回全部结果资料,in 不管匹配到匹配不到都 全部匹配完毕,使用ex ...

  7. PHP 5 MySQLi 函数

    在 PHP 中使用 MySQLi 函数需要注意的是:你需要添加对 MySQLi 扩展的支持. PHP MySQLi 简介 PHP MySQLi = PHP MySQL Improved! MySQLi ...

  8. MVC和MTV模式

    著名的MVC模式:所谓MVC就是把web应用分为模型(M),控制器(C),视图(V)三层:他们之间以一种插件似的,松耦合的方式连接在一起. 模型负责业务对象与数据库的对象(ORM),视图负责与用户的交 ...

  9. postgresql跨服务器复制数据库

    假设名为dbname数据库需要从A服务器拷贝到B服务器 接收服务器B postgres用户 需先重置B服务器postgres系统用户的密码,使之与数据库用户postgres一致: passwd -d ...

  10. 自定义一个仿Spinner

    两个布局文件: adpter_list.xml <?xml version="1.0" encoding="utf-8"?> <LinearL ...