进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位。不同进程拥有独立的代码空间和内存资源,同一进程下的线程是共享当前进程的所有系统资源。
假设在某个情景下,一个线程内部各个函数需访问同一数据(内存空间),线程各个函数是独立的。对于该情景下,一般考虑到的是用全局或者静态变量。功能上是满足,但由于进程内所有线程是共享进程的资源的,全局或者静态变量会暴露给其他线程,降低程序的健壮性。
对此,linux系统提供了一种数据结构—— 线程私有数据(Thread-specific Data)。在线程内部,线程私有数据可以被线程内函数访问,并屏蔽其他线程。线程私有数据,虽然也是全局变量,但其本质是"键值-值"(key-value
)的数据结构,并且是一键多值,每个线程都拥有独立的键值空间,线程间访问时,不影响除自身外其他线程的私有键值。相比普通的全局或静态变量,线程私有数据保证共享数据的安全和唯一性。
1. 线程私有数据
引入线程私有数据,有几大好处:
- 私有数据键值的连续性和唯一性,可用来作线程的区分和管理
- 基于进程的接口适应多线程环境,如系统编程中常用的
errno
返回错误值;在线程之前errno
被定义成进程环境全局变量,线程出现以后,为了让线程也能使用那些原本基于进程的系统调用和库例程,errno
被重新定义成线程私有数据 - 线程私有数据的只对本线程函数访问有效,避免资源竞争,提高线程数据独立性和程序健壮性
1.1 创建
int pthread_key_create(pthread_key_t *key, void (*destr_function) (void*));
参数 | 说明 |
---|---|
引用 | #include <pthread.h> |
key | 私有数据键 |
destr_function | 回调函数 |
返回 | 成功返回0 |
- destr_function:资源析构回调函数,一般用于线程退出时,执行资源释放操作和用户一些任务处理操作。传入NULL ,则调用系统默认资源析构函数。
- 线程共享进程资源,因此任一线程调用
pthread_key_create
创建的键,其他线程都可以访问,但各个线程的键值是私有的,针对线程内有效访问,实现一键多值。 - 创建一个线程私有数据键,必须保证
pthread_key_create
对于每个pthread_key_t
变量仅仅被调用一次,因为如果一个键被创建两次,其实是创建两个不同的键,第二个键将覆盖第一个键,可能导致第一个键以及其关联的线程私有键值数据丢失。因为创建新键时,每个线程的私有键值数据地址设为NULL。
1.2 数据关联
int pthread_setspecific (pthread_key_t key, const void *value);
参数 | 说明 |
---|---|
引用 | #include <pthread.h> |
key | 私有数据键 |
value | 待关联数据地址 |
返回 | 成功返回0 |
key值所关联的私有数据地址,可以用malloc/new来分配,但在线程退出前需free/delete,否则导致内存泄漏。
1.3 读取数据
void *pthread_getspecific(pthread_key_t key);
参数 | 说明 |
---|---|
引用 | #include <pthread.h> |
key | 私有数据键 |
返回 | 成功返回线程私有键值地址 |
函数返回的是存储键值的地址空间,可通过地址转换的方式获取键值数据。
int value = 0;
value = *(int *)pthread_getspecific(s_key);
1.4 释放
int pthread_key_delete(pthread_key_t key);
参数 | 说明 |
---|---|
引用 | #include <pthread.h> |
key | 私有数据键 |
返回 | 成功返回0 |
-
pthread_key_delete
删除一个私有键,仅仅是将该key所在结构体中数组pthread_keys
对应的元素设置为“un_use”,与key相关联的线程私有数据键值是不会被释放的,因此线程私有数据键值必须在键删除前释放。如键值是通过手动申请(malloc/new)内存的,必须在线程退出前释放,否则导致内存泄露。 -
当线程调用
pthread_exit
退出或者线程执行返回时,析构函数就会被调用,如果线程调用了exit
系列函数或者abort
或者其它非正常退出时,则不会调用释放函数。 -
线程退出时要执行用户自定义的析构函数,则
pthread_key_create
时指定函数地址。
2. 写个例子
- 创建线程私有数据
- 创建2个线程,分别关联私有数据键值
- printf线程id、私有数据键地址、私有数据键值
- 线程退出
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_key_t s_key; /* 线程私有数据 */
void echo(void)
{
printf("thread0 id [0x%lx], private key address [%p], private key value [0x%x]\n",
pthread_self(), pthread_getspecific(s_key), *(int *)pthread_getspecific(s_key));
}
void *thread0(void *arg)
{
long temp = 0;
temp = (long)arg;
pthread_setspecific(s_key, &temp); /* 改变私有数据 */
sleep(1);
echo();
}
void *thread1(void *arg)
{
long temp = 0;
temp = (long)arg;
pthread_setspecific(s_key, &temp); /* 改变私有数据 */
sleep(2);
echo();
}
int main(int argc, char **argv)
{
pthread_t tid0,tid1;
pthread_key_create(&s_key, NULL); /* 创建私有数据 */
pthread_create(&tid0, NULL, thread0, (void*)0x10);
pthread_create(&tid1, NULL, thread1, (void*)0x20);
pthread_join(tid0, NULL);
pthread_join(tid1, NULL);
pthread_key_delete(s_key); /* 释放私有数据 */
return 0;
}
s_key
作为静态变量的线程私有数据,线程0和线程1都可以访问,在不同线程中关联的键值数据不同。echo
函数作为打印私有数据地址和键值,在不同的线程中调用时,输出不同的结果。