在单线程程序中,函数经常使用全局变量或静态变量,这是不会影响程序的正确性的,但如果线程调用的函数使用全局变量或静态变量,则很可能引起编程错误,因为这些函数使用的全局变量和静态变量无法为不同的线程保存各自的值,而当同一进程内的不同线程几乎同时调用这样的函数时就可能会有问题发生。而解决这一问题的一种方式就是使用线程特定数据机制。
static char str[100];
void A(char* s)
{
strncpy(str,s,100);
}
void B()
{
printf("%s\n",str);
}
在上面这个例子中,可以想象,如果在多线程程序中,各个线程都依次调用函数A和函数B,那么某些线程可能得不到期望的显示结果,因为它使用B显示的字符串可能并不是在A中设置的字符串。
Key结构数组
POSIX要求实现POSIX的系统为每个进程维护一个称之为Key的结构数组,这个数组中的每一个结构称之为一个线程特定数据元素。POSIX规定系统实现的Key结构数组必须包含不少于128个线程特定数据元素,而每个线程特定数据元素中至少包含两项内容:使用标志和析构函数指针。
Key结构数组中每个元素的索引(0~127)称之为键(key),当一个线程调用pthread_key_create创建一个新的线程特定数据元素时,系统搜索其所在进程的Key结构数组,找出其中第一个不在使用的元素,并返回该元素的键。这个函数的形式为:
int pthread_key_create(pthread_key_t keyptr, void ( destructor)(void *value));
参数keyptr为一个pthread_key_t变量的指针,用于保存得到的键值。参数destructor为指定的析构函数的指针。
除了Key结构数组,系统还在进程中维护关于每个线程的多种信息。这些特定于线程的信息被保存于称之为Pthread的结构中。Pthread结构中包含名为pkey的指针数组,其长度为128,初始值为空。这128个指针与Key结构数组的128个线程特定数据元素一一对应。在调用pthread_key_create得到一个键之后,每个线程可以依据这个键操作自己的pkey指针数组中对应的指针,这通过pthread_getspecific和pthread_setspecific函数来实现。这两个函数的形式为:
void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);
pthread_getspecific返回pkey中对应于key的指针,而pthread_setspecific将pkey中对应于key的指针设置为value。
我们使用线程特定数据机制,就是要使线程中的函数可以共享一些数据。如果我们在线程中通过malloc获得一块内存,并把这块内存的指针通过pthread_setspecific设置到pkey指针数组中对应于key的位置,那么线程中调用的函数即可通过pthread_getspecific获得这个指针,这就实现了线程内部数据在各个函数间的共享。当一个线程终止时,系统将扫描该线程的pkey数组,为每个非空的pkey指针调用相应的析构函数,因此只要将执行回收的函数的指针在调用pthread_key_create时作为函数的参数,即可在线程终止时自动回收分配的内存区。
例
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#define LEN 100
pthread_key_t key;
void A(char *s)
{
char *str = (char*)pthread_getspecific(key);
strncpy(str, s, LEN);
}
void B()
{
char *str = (char*)pthread_getspecific(key);
printf("%s\n", str);
}
void destructor(void *ptr)
{
free(ptr);
printf("memory freed\n");
}
void *threadfunc1(void *pvoid)
{
pthread_setspecific(key, malloc(LEN));
A("Thread1");
B();
}
void *threadfunc2(void *pvoid)
{
pthread_setspecific(key, malloc(LEN));
A("Thread2");
B();
}
int main()
{
pthread_t tid1, tid2;
pthread_key_create(&key, destructor);
pthread_create(&tid1, NULL, &threadfunc1, NULL);
pthread_create(&tid2, NULL, &threadfunc2, NULL);
pthread_exit(NULL);
return 0;
}
在这个程序中,函数A和函数B共享了一个内存区,而这个内存区是特定于调用A和B的线程的,对于其它线程,这个内存区是不可见的。这就安全有效地达到了在线程中的各个函数之间共享数据的目的。
运行结果
运行分析
首先定义一个全局变量pthread_key_t key,然后在main中用pthread_key_create创建一个当前进程专用的Key数组,他的析构函数为destructor,当进程结束以后,程序就会自动检查Key数组中已经使用的元素,把改元素作为输入参数,调用析构函数,从而释放掉内存。紧接着就是在main函数里面创建了两个线程,在线程中首先用pthread_setspecific从Key数组中获取一个对应当前进程的元素,然后让这个指针指向分配LEN大小的空间。最后就是用pthread_getspecific获取Key数组中对应该线程的指针,并对他操作。