线程私有数据(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. GC其他:引用标记-清除、复制、标记-整理的说明

    对象死亡历程 1.基本的mark&sweep是必须的,后续的都是对他的改进, 2.young代理的survivor就是使用了复制算法,避免碎片 3.还有标记整理算法(压缩),就是将存活的对象移 ...

  2. sea.js及三种加载方式的异同

      一.前言     浏览器本身并不提供模块管理的机制,过去网页开发中,为了使用各种模块,不得不在加入一大堆script标签.这样就使得网页体积臃肿,难以维护,还产生大量的HTTP请求,拖慢显示速度, ...

  3. MySQL的Explain关键字查看是否使用索引

    explain显示了MySQL如何使用索引来处理select语句以及连接表.可以帮助选择更好的索引和写出更优化的查询语句.简单讲,它的作用就是分析查询性能. explain关键字的使用方法很简单,就是 ...

  4. ubuntu + 远程桌面连接命令 + rdesktop + 连接windows或者ubuntu远程桌面

    原文 https://www.cnblogs.com/xiaouisme/p/5166469.html sudo apt-get install rdesktop rdesktop 124.42.12 ...

  5. MySql配置文件模板

    写在开篇:        这个mysql得配置文件my.cnf,是我现在环境里常用得,包含基础配置及一些优化,本来一直在我得有道笔记里记录着,之前一直没有写博客的习惯,最近刚开始注册博客,就将这些东西 ...

  6. Hadoop — MapReduce原理解析

    1. 概述 Mapreduce是一个分布式运算程序的编程框架,是用户开发"基于hadoop的数据分析应用"的核心框架: Mapreduce核心功能是将用户编写的业务逻辑代码和自带默 ...

  7. MySQL UPDATE 查询

    MySQL UPDATE 查询 如果我们需要修改或更新MySQL中的数据,我们可以使用 SQL UPDATE 命令来操作.. 语法 以下是 UPDATE 命令修改 MySQL 数据表数据的通用SQL语 ...

  8. ubuntu初始化python3+postgresql+uwsgi+nginx+django

    一. postgresql 数据库 安装 apt-get update apt-get install postgresql 进入psql客户端 sudo -u postgres psql 创建数据库 ...

  9. 《Non-Negative Matrix Factorization for Polyphonic Music Transcription》译文

    NMF(非负矩阵分解),由于其分解出的矩阵是非负的,在一些实际问题中具有非常好的解释,因此用途很广.在此,我给大家介绍一下NMF在多声部音乐中的应用.要翻译的论文是利用NMF转录多声部音乐的开山之作, ...

  10. J2EE中MVC的各层的设计原则及其编写注意事项

    总结了下J2EE的MVC模式开发原则,很多细节处理好了是很有利于开发与维护的. 下面就从各层说起. 视图层 主要是客户端的显示,主要是JSP和HTML,随着Web的不断发展,许多基于Javascrip ...