线程特定数据TSD总结

  • 一线程的本质
  • 二线程模型的引入
  • 三线程特定数据
  • 四关键函数说明
  • 五刨根问底啥原理
  • 六私有数据使用示例
  • 七参考文档

一、线程的本质

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

二、线程模型的引入

线程模型引入是为了数据共享,为什么又引入线程私有数据?有时候想让基于进程的接口适应多线程环境,这时候就需要为每个线程维护一份私有数据了,最典型的就是errno了。

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

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

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

鉴于这两个问题,我们可以借助线程的私有数据(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不会激活与键关联的析构函数,容易造成内存泄露。要释放任何与键对应的线程私有数据值的内存空间,需要在应用程序中采取额外的步骤。当删除线程私有数据键的时候,不会影响任何线程对该键设置的线程私有数据值,甚至不影响调用线程当前键值。建议最后才删除线程私有数据键,尤其当一些线程仍然持有该键的值时,就更不该释放该键。使用已经删除的私有数据键将导致未定义的行为。

五、刨根问底啥原理

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

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

运行结果

[email protected]:/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);
}

运行结果

[[email protected]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/

时间: 2024-08-10 14:11:19

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

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

线程特定数据 int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *)); int pthread_key_delete(pthread_key_t key); int pthread_setspecific(pthread_key_t key, const void *pointer); void * pthread_getspecific(pthread_key_t key); pthread_onc

pthread线程特定数据

线程特定数据,也被称为线程私有数据,是一种存储和查找一个特定线程相关数据的机制.我们称这个数据为线程特定或线程私有的原因,是因为每个线程访问它自己独立的数据拷贝,而不用担心和其它线程的访问的同步. 线程特定数据看似很复杂,其实我们可以把它理解为就是一个索引和指针.key结构中存储的是索引,pthread结构中存储的是指针,指向线程中的私有数据,通常是malloc函数返回的指针. POSIX要求实现POSIX的系统为每个进程维护一个称之为Key的结构数组(如图1所示),这个数组中的每个结构称之为一

线程特定数据

1 #include <pthread.h> 2 #include <stdio.h> 3 #include <string.h> 4 #include <stdlib.h> 5 6 7 pthread_key_t thread_self_data_key; 8 9 void destory(void *p) 10 { 11 free(p); 12 } 13 14 void createit(void) 15 { 16 pthread_key_create(

线程私有数据(TSD) 【转载】

出处:http://www.cnblogs.com/yuxingfirst/archive/2012/07/25/2608612.html 线程中特有的线程存储, Thread Specific Data .线程存储有什么用了?他是什么意思了?大家都知道,在多线程程序中,所有线程共享程序中的变量.现在有一全局变量,所有线程都可以使用它,改变它的值.而如果每个线程希望能单独拥有它,那么就需要使用线程存储了.表面上看起来这是一个全局变量,所有线程都可以使用它,而它的值在每一个线程中又是单独存储的.这

为线程特定数据创建键

#include <stdlib.h> #include <pthread.h> #include <stdio.h> #include <sched.h> #include<string.h> pthread_key_t key; void destructor(void *data) //如果创建该键时指定了destructor 函数,则该线程终止时,系统会调用destructor 函数,传进的参数是绑定的值. { if(data != NU

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

在单线程程序中,我们经常要用到"全局变量"以实现多个函数间共享数据, 然而在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有.但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问.POSIX线程库通过维护一定的数据结构来解决这个问题,这个些数据称为(Thread-specific-data或 TSD). 相关函数如下: int pthread_key_create(pthread_key_t *key, void (*destr

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

多线程编程之线程私有数据 Pthread是 POSIX threads 的简称,是POSIX的线程标准.  线程同步从互斥量[C/C++多线程编程之六]pthread互斥量,信号量[C/C++多线程编程之七]pthread信号量,条件变量[C/C++多线程编程之八]pthread条件变量,读写锁[C/C++多线程编程之九]pthread读写锁,多线程的同步机制已经有了清晰深入的探究,多线程编程的精髓所在,需要深入理解.        线程私有数据TSD(Thread-specific Data)

12.6 线程私有数据

线程私有数据是一种用于存储和获取与特定线程相关联数据的机制,称为线程特定的或者是线程私有的,是因为我们希望每个线程都可以独立访问其独有的数据,而不用担心与其他线程的同步访问问题. 许多人费力实现了促进进程数据以及属性贡献的线程模型,那么为什么还有人想要实现一个接口,在这样一个模型中防止共享呢?有如下两点原因: 首先,有些时候我们需要以线程为基础维护一些数据,因为没有任何机制可以保证线程ID总是比较小的,且是连续的整数,因此我们不能简单地将每一个线程的私有数据分配为一个数组,然后使用线程ID作为索

多线程06-多线程共享数据的方式(经验小结)

1.案例分析-01 通过代码实现火车票出售的例子 在实现代码之前先对问题进行分析:火车票出售应该是在多个窗口进行的(即多个线程),以一个车的班次来说,该班次的火车票张数即为多个窗口共享的数据 即这份共享数据为出售特定班次的火车票,这个动作在多个窗口都是不变的,变更的只有火车票的剩余张数.代码实现如下: package org.lkl.thead; /** * * Function : 多线程共享数据 * * @author : Liaokailin CreateDate : 2014-6-13