一、线程的本质

Linux线程又称轻量进程(LWP),也就说线程本质是用进程之间共享用户空间模拟实现的。

二、线程模型的引入

线程模型引入是为了数据共享,为什么又引入线程私有数据?

有时候想让基于进程的接口适应多线程环境,这时候就须要为每一个线程维护一份私有数据了。最典型的就是errno了。

在维护每一个线程的私有数据的时候,我们可能会想到分配一个保存线程数据的数组,用线程的ID作为数组的索引来实现訪问。

1. 系统生成的线程ID不能保证是一个小而连续的整数

2. 用数组实现的时候easy出现越界读写的情况

鉴于这两个问题,我们能够借助线程的私有数据(TSD)来解决问题。

三、线程特定数据

线程私有数据(Thread Specific Data)。是存储和查询与某个线程相关的数据的一种机制。把这样的数据称为线程私有数据或线程特定数据的原因是,希望每一个线程能够独立地訪问数据副本,从而不须要考虑多线程同步问题。

提示:进程中的全部线程都能够訪问进程的整个地址空间。除了使用寄存器以外(一个线程真正拥有的唯一私有存储是处理器的寄存器)。线程没有办法阻止其它线程訪问它的数据,线程私有数据也不例外。

尽管底层的实现部分并不能阻止这样的訪问能力,但管理线程私有数据的函数能够提高线程间的数据独立性。

四、关键函数说明

  • int pthread_key_create(pthread_key_t *keyp,void (*destructor)(void *));

    返回值:若成功则返回0,否则返回错误编号

    功能:创建的键存放在keyp指向的内存单元,这个键能够被进程中的全部线程使用。但每一个线程把这个键与不同的线程私有数据地址进行关联。

    说明:

    1. 创建一个线程私有数据键。必须保证对于每一个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。

    2. 当线程调用pthread_exit或者线程执行返回。正常退出时,假设私有数据不为空且注冊了析构函数。析构函数就会被调用,但假设线程调用了exit_exit_Exitabort或出现其它非正常的退出时就不会调用析构函数注1

    3. 线程能够为线程私有数据分配多个键注2,每一个键都能够有一个析构函数与它关联。各个键的析构函数能够互不同样,当然它们也能够使用同样的析构函数。

    4. 线程退出时,线程私有数据的析构函数将依照操作系统实现中定义的顺序被调用。析构函数可能会调用还有一个函数,该函数可能会创建新的线程私有数据并且把这个数据与当前的键关联起来。当全部的析构函数都调用完毕以后,系统会检查是否还有非null的线程私有数据值与键关联,假设有的话,再次调用析构函数。这个过程会一直反复直到线程全部的键都为null值线程私有数据。或者已经做了最大次数的尝试注3
    5. 创建新键时,每一个线程的数据地址设为NULL。
  • int pthread_key_delete(pthread_key_t *keyp);

    返回值:若成功则返回0,否则返回错误编号

    功能:取消键与线程私有数据值之间的关联关系。

    说明:

    1. 调用pthread_delete不会激活与键关联的析构函数,easy造成内存泄露。要释放不论什么与键相应的线程私有数据值的内存空间。须要在应用程序中採取额外的步骤。当删除线程私有数据键的时候。不会影响不论什么线程对该键设置的线程私有数据值,甚至不影响调用线程当前键值。建议最后才删除线程私有数据键。尤其当一些线程仍然持有该键的值时,就更不该释放该键。使用已经删除的私有数据键将导致没有定义的行为。

五、刨根问底啥原理

线程私有数据实现的主要思想,如图所看到的



系统内部为每一个进程维护了两种数据,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/7901248

http://blog.chinaunix.net/uid-8917757-id-2450452.html

http://www.3fwork.com/b902/001375MYM016745/

线程特定数据TSD总结的更多相关文章

  1. Linux多线程实践(4) --线程特定数据

    线程特定数据 int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *)); int pthread_key_ ...

  2. pthread线程特定数据

    举个栗子 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/t ...

  3. 线程的属性和 线程特定数据 Thread-specific Data

    一.posix 线程属性 POSIX 线程库定义了线程属性对象 pthread_attr_t ,它封装了线程的创建者可以访问和修改的线程属性.主要包括如下属性: 1. 作用域(scope) 2. 栈尺 ...

  4. muduo网络库源码学习————线程特定数据

    muduo库线程特定数据源码文件为ThreadLocal.h //线程本地存储 // Use of this source code is governed by a BSD-style licens ...

  5. linux线程私有数据---TSD池

    进程内的所有线程共享进程的数据空间,所以全局变量为所有线程共有.在某些场景下,线程需要保存自己的私有数据,这时可以创建线程私有数据(Thread-specific Data)TSD来解决.在线程内部, ...

  6. 线程私有数据TSD——一键多值技术,线程同步中的互斥锁和条件变量

    一:线程私有数据: 线程是轻量级进程,进程在fork()之后,子进程不继承父进程的锁和警告,别的基本上都会继承,而vfork()与fork()不同的地方在于vfork()之后的进程会共享父进程的地址空 ...

  7. LInux多线程编程----线程特定数据的处理函数

    1.pthread_key_t和pthread_key_create() 线程中特有的线程存储, Thread Specific Data .线程存储有什么用了?他是什么意思了?大家都知道,在多线程程 ...

  8. Linux多线程实践(四 )线程的特定数据

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

  9. pthread线程私有数据

    进程内所有的线程共享地址空间,任何声明为静态或外部的变量,或在进程堆声明的变量都可以被进程内的所有线程读写. static,extern,或堆变量的值是上次线程改写的值 一个线程真正拥有的唯一私有存储 ...

随机推荐

  1. Python9-day2 作业

    下列结果是什么? 6 or 2 >1     6 3 or 2 > 1  3 0 or 5<4  false 5 < 4 or 3  3 2 >1 or 6 True 3 ...

  2. iOS UITextView点击事件处理

    自定义一个UITextView UITextView 的selectedRange 影响 selectedTextRange 改变前者可影响后者 self.selectedRange -->se ...

  3. poj 3783

    Balls Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 1196   Accepted: 783 Description ...

  4. debian 9 安装AMD驱动

    目录 debian 9 安装AMD驱动 安装驱动之前: 安装驱动: 安装驱动之后: debian 9 安装AMD驱动 需求说明: 安装完成debian系统后独显驱动未安装 操作系统版本: kyeup@ ...

  5. 学习笔记4——WordPress插件介绍

    1.什么是WordPress插件? WordPress有三大组件:核心.主题.插件. 插件是扩展了WordPress核心功能的代码包.WordPress插件由PHP代码和其他资源(如图像,CSS和JS ...

  6. Leetcode 324.摆动排序II

    摆动排序II 给定一个无序的数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]... 的顺序. 示例 1: 输入: nums ...

  7. shell-001

    for: shell_test #!/bin/bash var= var=$var+ echo $var mkdir only_a_joke shell_joke #!/bin/bash ./shel ...

  8. ASP.NET(五):ASP.net实现真分页显示数据

    导读:在上篇文章中,介绍了用假分页实现数据的分页显示 ,而避免了去拖动滚动条.但,假分页在分页的同时,其实是拖垮了查询效率的.每一次分页都得重新查询一遍数据,那么有没有方法可以同时兼顾效率和分页呢,那 ...

  9. Android单个按钮自定义Dialog

    代码改变世界 Android单个按钮自定义Dialog dialog_layout.xml <?xml version="1.0" encoding="utf-8& ...

  10. Shell脚本学习指南 [ 第一、二章 ] 背景知识、入门

    摘要:第一章介绍unix系统的发展史及软件工具的设计原则.第二章介绍编译语言与脚本语言的区别以及两个相当简单但很实用的Shell脚本程序,涵盖范围包括了命令.选项.参数.Shell变量.echo与pr ...