线程私有数据的介绍与使用(TSD)

本文详细介绍了线程私有数据(TSD)的概念及其在POSIX系统中的使用方法,包括如何创建和销毁键、如何绑定和获取线程私有数据,以及线程退出时如何自动调用析构函数释放资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                            线程私有数据的介绍与使用(TSD)

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

POSIX系统要求每个进程有一个Key(键值)结构数组,这个结构体数组至少包含128个元素,每个元素至少包含两项内容,分别是这个元素的使用标志(这个元素是否被使用了)和这个元素的析构函数的指针,使用标志初始值是未被使用。每个元素(键值)是一个进程的全局变量,所有线程都可以使用该元素(键值),通过以下函数生成该键值:

pthread_key_t key;(一个键值类型的变量)

pthread_key_creat(pthread_key_t * ptr_key, void ( *destruction)(void *arg))

该函数成功完成之后返回0,其他任何返回值都表明错误,例如:EAGAIN:名称空间已用完;ENOMEM:进程中虚拟内存不足,无法创建新键;

如果一个系统的Key结构数组包含128个元素,那么每个元素的索引从0到127就是这个键值,那么上面这个函数搜索其所在进程的这个结构数组,知道找到第一个未被使用的键,将这个键值作为值参传递给上述函数的第一个参数,同时注册该键值的析构函数,当某个线程对这个键值关联了自己的线程私有数据时,当这个线程正常退出时(调用pthread_exit()),就会调用该析构函数,析构函数的作用一般是释放该线程私有数据所占据的内存单元,不然就会出现内存泄露的情况。

每个线程内部的数据在进程中由一个Pthread的数据结构来维护,其中包括了每个线程的私有数据TSD,Pthread结构中包含名为pkey的指针数组,也是一个包含128个元素的数组,初始值为空(NULL),这128个指针与Key结构数组里面的128个元素一一对应,(因此,从理论上说每个线程可以有128个线程私有数据,自己这么认为的),在调用pthread_key_creat()得到一个键之后,每个线程可以依据这个键操作自己的Pkey数组中对应的指针(也就是线程私有数据的地址),这通过int pthread_setspecific(pthread_key_t key, const void *value);void *pthread_getspecific(pthread_key_t key);两个函数来实现。pthread_setspecific函数将与key对应的Pkey指针数组中的值设为value,就像将这个线程私有数据的地址(value)绑定到该key上,如果不是第一次绑定到该键值,一定要先释放原来的TSD,然后再绑定该键到下一个TSD;pthread_getspecific()函数将key对应的Pkey中对应的指针数组的值返回作为该函数的返回值。

值得注意的是:相同的关键字在可以被进程里所有的线程所使用,但是每个线程都将关键字绑定到自己的线程私有数据上面,创建一个关键字时,每个线程对应的Pkey数组中的对应元素的值都初始化NULL,直到调用pthread_setspecific初始化绑定之后。

当你想把进程中的全局键(key)销毁时,可以调用pthread_key_delete(pthread_key_t key),该函数会直接销毁该键,并且也会相应的销毁与这个键对应的进程中所有线程的TSD,此外,该函数调用时不会检查是否有线程正在使用该键对应的线程的TSD,因此,在调用该函数之前最好先调用析构函数释放TSD分配的内存空间。也就是说最好将该函数调用放在主线程中,而其他初始化了TSD的线程在调用pthread_exit()的时候释放TSD,最后主线程再调用pthread_key_delete()结束。

 

线程的析构函数一般用来释放分配的TSD的内存空间,是通过destructive(void *arg)这里的arg参数来传递的,arg就等同于函数pthread_setspecific(pthread_key_t key,void *value)里的value值。也就是线程私有数据的地址,记住在value前面最好不要加地址,否则从析构函数出获取该TSD时会出错。

 

 

最后本文参考了链接中的文章以及其它的文章:http://xingyunbaijunwei.blog.163.com/blog/static/76538067201292911231312/

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

<think>嗯,用户想了解线程私有数据的概念、作用和实现方式。我需要先回忆一下线程的基础知识。线程之间通常共享进程的资源,比如全局变量和堆内存,这可能导致数据竞争和同步问题。这时候,线程私有数据TSD)就派上用场了,它允许每个线程拥有独立的数据副本,避免竞争。 根据引用[3],TSD使用一键多值的技术,一个键对应不同线程中的不同数据。这样,每个线程通过相同的键访问自己的私有数据。实现方式可能涉及创建和销毁键,以及关联数据到键上。引用[5]提到TSD是同key不同value,访问通过键来获取各自的值。 接下来是具体实现步骤。首先需要创建键,比如使用pthread_key_create函数。然后,每个线程可以用pthread_setspecific将数据关联到键,用pthread_getspecific获取数据。销毁键的时候用pthread_key_delete,但要注意先释放每个线程的数据,避免内存泄漏。 作用方面,TSD主要用于避免竞争,比如将errno设为线程私有,或者维护线程的独立上下文,如数据库连接。引用[4]提到线程池中使用TSD可以实现无竞争的内存申请,说明实际应用中的有效性。 需要注意线程退出时的资源释放,可能需要析构函数。另外,键的创建应在所有线程使用之前,通常在主线程初始化时完成。需要确保这些步骤正确,避免数据错乱或泄漏。 用户可能的问题会涉及如何具体使用这些函数,TSD应用场景,或者其他同步机制的区别。需要准备好这些相关问题的回答,帮助用户深入理解。</think>### 线程私有数据的作用实现方式 #### 一、线程私有数据的作用 线程私有数据(Thread-Specific Data, TSD)是一种允许**每个线程拥有独立数据副本**的机制,其核心作用包括: 1. **避免数据竞争**:在多线程环境中,全局变量或静态变量会被所有线程共享,可能导致数据竞争。TSD通过为每个线程提供独立的数据副本,消除竞争条件[^3][^5]。 2. **维护线程上下文**:某些场景需要线程保存自身状态(如错误码`errno`、数据库连接句柄等),TSD可实现线程间数据的隔离[^2][^4]。 3. **兼容旧代码**:在将单线程代码改造为多线程时,TSD可隔离原本依赖全局变量的逻辑,减少代码改动[^3]。 #### 二、实现方式 TSD的实现基于**一键多值**技术:一个全局的键(key)关联到不同线程私有数据值(value)。以下是关键步骤: 1. **创建线程私有数据键** 使用`pthread_key_create`创建全局唯一的键,并为该键注册析构函数(用于线程退出时释放资源): ```c pthread_key_t key; pthread_key_create(&key, destructor_function); ``` 2. **关联数据到当前线程** 通过`pthread_setspecific`将数据当前线程绑定: ```c void *data = malloc(sizeof(my_struct)); pthread_setspecific(key, data); // 将数据关联到当前线程的键 ``` 3. **读取线程私有数据** 使用`pthread_getspecific`获取当前线程关联的数据: ```c void *data = pthread_getspecific(key); // 仅访问当前线程的数据 ``` 4. **销毁键** 在所有线程释放数据后,调用`pthread_key_delete`销毁键: ```c pthread_key_delete(key); ``` #### 三、应用示例 以下代码演示了线程私有数据的典型用法(以错误码隔离为例): ```c #include <pthread.h> #include <stdlib.h> static pthread_key_t errno_key; void destructor(void *data) { free(data); // 线程退出时释放错误码内存 } void init_errno_key() { pthread_key_create(&errno_key, destructor); } int *get_thread_errno() { int *err = pthread_getspecific(errno_key); if (!err) { err = malloc(sizeof(int)); *err = 0; pthread_setspecific(errno_key, err); } return err; } ``` #### 四、注意事项 - **键的初始化**:键的创建(`pthread_key_create`)应在所有线程使用前完成,通常由主线程初始化[^2]。 - **内存管理**:需手动管理线程私有数据的内存,析构函数负责释放资源。 - **线程安全**:键的创建和删除本身非线程安全,需配合锁或单次初始化机制。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值