在学习线程操作的过程中,我一直对线程私有数据的作用很困惑,不知道它到底有什么作用,但经过一段时间的探索后,我总结了一部分关于线程私有数据的用法,如果有不对或者不够详细的地方,请指出。
线程私有数据的实现:
线程的私有数据采用了一种被称为 "一键多值" 的技术,即一个键对应多个数值。
操作线程私有数据的函数主要有4个:
1> pthread_key_create(); 创建一个键
2> pthread_key_delete(); 删除一个键
3> pthread_setspecific(); 将一个私有数据与键相关联
4> pthread_getspecific(); 从一个键读取线程私有数据
这几个函数的声明如下:
#include <pthread.h>
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) ;
int pthread_getspecific (pthread_key_t key) ;
函数的作用:
1> pthread_key_create():从Linux的TSD池中分配一项,将其值分配给第一个参数指向的key供以后访问使用,函数的第二个参数是一个函数指针,如果指针不为空,则在线程退出时将 以key所关联的数据为参数 调用destr_function(),释放分配的缓冲区。
2> phread_delete():该函数用来删除一个键。删除后,键所占的内存将被释放。需要注意的是,与该键关联的线程数据所占用的内存并不被释放。因此,线程数据的释放必须在释放键之前完成。
3> pthread_setspecific():该函数将pointer的值 (不是内容) 与key相关联。用pthread_setspecific为一个键指定新的线性数据时,线程必须先释放原有的线程数据用以回收空间。
4> pthread_getspecific():通过该函数得到与key相关联的数据。
"一键多值" 技术的实现靠的是一个关键数据结构数组,即TSD池,其结构如下:
static struct pthread_key_struct pthread_keys [PTHREAD_KESYS_MAX] = { {0,NULL} };
线程私有数据的用法:
线程分配私有数据之前,创建与该数据相关联的键,这个键可以被进程中所有线程使用,但每个线程把这个键与不同的线程私有数据地址进行关联。每次线程需要使用该私有数据时,就可以通过该键来获得私有数据的值。
线程私有数据的作用:
线程的私有数据需要和 线程函数的局部变量 区分开,这两者之间最主要的区别在于作用范围不同。
线程的局部变量虽然也属于线程的私有数据,但它的作用范围只在线程函数中,对于线程中调用的其他函数来说,该变量是不可见的,除非线程函数将其当作参数传入所调用的函数中。
线程的私有数据则相当于线程中的全局变量,由于创建的键key是一个进程中的全局变量,所以进程中的所有函数都可以访问key,但键的特性使每个访问它的线程都会获得不同的值,即线程提前与其关联的数据。所以,当线程函数中的一个数据与key相关联后,对于线程所调用的每一个函数来说,该数据都是可见的。
简单来讲,将线程中的一个数据变为线程的私有数据,就是将该数据的作用范围扩大到整个线程,变成了该线程中的全局变量,对于其他的线程来说,该线程的全局变量是不可见的。而线程中的局部变量作用范围仅限于定义该局部变量的函数,对于同一个线程调用的其他函数来说,该变量是不可见的。
为了更好的理解私有数据的作用,可以参照下列代码:
/*************************************************************************
> File Name: pthread_test.c
> Author: zhaobaojin
> BLOG: blog.csdn.net/zhaobaojin1006
> Mail: 1279742553@qq.com
> Created Time: 2015年07月30日 星期四 14时38分59秒
************************************************************************/
#include<stdio.h>
#include<pthread.h>
pthread_key_t key ; /*创建一个键*/
int func(void) ;
void thread1(void *arg) ; /*线程函数*/
void thread2(void *arg) ;
int main(void)
{
int *status ;
pthread_t thid1, thid2 ;
pthread_key_create (&key, NULL) ;
if (pthread_create (&thid1, NULL, (void *)thread1, NULL) != 0 ) { /*创建新线程1*/
printf ("new thread create failed\n") ;
return 0 ;
}
if (pthread_create (&thid2, NULL, (void *)thread2, NULL) != 0 ) { /*创建新线程2*/
printf ("new thread create failed\n") ;
return 0 ;
}
pthread_join (thid1, (void *)&status) ;
pthread_join (thid2, (void *)&status) ;
pthread_key_delete (key) ;
return 0 ;
}
int func()
{
/*新线程中调用函数打印key*/
printf ("i am newthread:%u,i can read the key:%d\n", pthread_self(), pthread_getspecific (key)) ;
return 0 ;
}
void thread1(void *arg) /*线程1将一个线程函数中的局部变量跟key关联,调用func()打印跟key关联的值*/
{
int tsd=5 ;
pthread_setspecific( key , (void *)tsd ) ; /*关联私有数据*/
func();
pthread_exit(0);
}
void thread2(void *arg) /*线程2关联同一个key,并调用func()打印跟key关联的值*/
{
int tsd=6 ;
pthread_setspecific( key , (void *)tsd ) ; /*关联私有数据*/
func();
pthread_exit(0);
}
程序中 thread1, thread2 是两个线程函数,都将自己的局部变量与key相关联,也都调用同一个 func() 函数来打印key中所存储的值。
运行结果如下:
从运行结果可以看出,不同线程调用 pthread_getspecific() 读取的key键值是不同的,而同一线程中调用的不同函数都可以直接获得私有数据,也即私有数据作用范围扩大到了线程的全局变量。
需要注意的是,在编译这段代码时,编译器会报警告,原因是代码中的
int tsd=5 ;
pthread_setspecific( key , (void *)tsd ) ; /*关联私有数据*/
这两句代码很容易可以看出,程序将一个整型变量 tsd 强制类型转换成了 void 型指针,函数之所以 这样设计的是因为,操作系统中的 TSD池 提前并不知道线程需要存入的数据类型,故而将所有数据都先统一转换为 (void *) 型,到需要读取私有数据时再转换为原本的数据类型。
printf ("i am newthread:%u,i can read the key:%d\n", pthread_self(), pthread_getspecific (key)) ;
读取私有数据的函数 pthread_getspecific() 返回值也是 (void *) 型,程序中私有数据在输出时则以 %d 类型输出。