【Linux应用编程】线程私有数据

  进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位。不同进程拥有独立的代码空间和内存资源,同一进程下的线程是共享当前进程的所有系统资源。

  假设在某个情景下,一个线程内部各个函数需访问同一数据(内存空间),线程各个函数是独立的。对于该情景下,一般考虑到的是用全局或者静态变量。功能上是满足,但由于进程内所有线程是共享进程的资源的,全局或者静态变量会暴露给其他线程,降低程序的健壮性。

  对此,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函数作为打印私有数据地址和键值,在不同的线程中调用时,输出不同的结果。


3. 参考

【1】linux线程私有数据详解
【2】linux下线程私有数据实现原理及使用方法总结

原创文章 128 获赞 147 访问量 36万+

猜你喜欢

转载自blog.csdn.net/qq_20553613/article/details/105123549
今日推荐