在单线程程序中,我们经常要用到"全局变量"以实现多个函数间共享数据。在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问.
我们想想使用什么方法可以做到这一点。
1.全局变量为进程中所有线程可见,是否有方法可以缩小其可见度,使其只在某个线程中可见
2.函数模块中的自动变量,只在本函数内可直接访问,并伴随该函数消亡。然而堆中malloc出来的一块内存,它消亡是手动实现的。第一,想办法让其地址被某个线程全局可见,即这个线程的所有模块可以直接访问到它。第二,使其伴随该线程消亡,即生命周期为线程生命周期。满足这两点可以认为其为某线程的私有全局变量。
因为,进程中的所有线程终究是同在一个地址空间,所以其他线程终究可能使用一些底层技术来达到访问其他线程中的数据,线程私有数据也不例外。但是通过底层实现线程私有数据机制,然后用函数封装起来,屏蔽底层细节,能给上层应用层提供线程私有数据机制,给应用提供方便,提高线程间数据独立性。
POSIX的实现类似上述的第2种点方法。
系统在进程内维护关于每个线程的多条信息,这些特定于线程的信息我们称之为pthread结构。因为它是特定于某个线程的,所以可以考虑将线程私有数据附加在pthread结构上,像搭顺风车一样。所以我觉得它是在利用操作系统内核实现的。《UDP》第三版的第26章有一节是关于线程特定数据的,该节比较详细介绍了这种实现原理,比《APUE》中关于线程私有数据那节要详细的多。
系统(可能是线程函数库)为每个进程维护一个我们称之为Key结构的结构数组。在LinuxThreads的实现中,TSD(Thread-specific Data)池用一个结构数组表示:
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } }; |
每个系统支持有限数量的线程私有数据PTHREAD_KEYS_MAX.
Posix定义了两个API分别用来创建和注销TSD:
int pthread_key_create(pthread_key_t *keyptr, void (*destructor)(void *value)) |
创建一个TSD就相当于将结构数组中的标志设置为"in_use",并将其索引返回给*key,然后设置destructor函数为destr_function。该函数从TSD池中分配一项,将其值赋给key供以后访问使用。如果destr_function不为空,在线程退出(pthread_exit())时将以key所关联的数据(pthread_setspecific函数的第二个参数,即对应pkey中的值)为参数调用destr_function(),以释放分配的缓冲区。不论哪个线程调用pthread_key_create(),所创建的key都是所有线程可访问的。
注销一个TSD采用如下API:
int pthread_key_delete(pthread_key_t key) |
这个函数并不检查当前是否有线程正使用该TSD,也不会调用清理函数(destr_function),而只是将TSD释放以供下一次调用pthread_key_create()使用。在LinuxThreads中,它还会将与之相关的线程数据项设为NULL。
接下来将我们的线程私有数据添加到pthread结构中
TSD的读写都通过专门的Posix Thread函数进行,其API定义如下:
int pthread_setspecific(pthread_key_t key, const void *pointer) void * pthread_getspecific(pthread_key_t key) |
传入的pointer会被拷贝到pkey数组中,而pointer可以指向任何的线程私有数据,比如pointer为malloc出来的地址,再为其编写个析构函数free掉pointer,并将这个函数在pthread_key_create时候,通过destructor参数注册进来。线程退出时,会自动调用。
相应的读出函数pthread_getspecific则将与key相关联的数据读出来,即pkey中存的值。
线程内的任何函数都可以调用pthread_getspecific获取到私有数据,而不同的线程调用相同的接口pthread_getspecific(key),得到的结果却不一样,像C++中的多态。
#include <stdio.h> #include <unistd.h> #include <pthread.h> #include <stdlib.h>
pthread_key_t key;
void echomsg(void *t) { int *tt=(int*)t; printf("destructor excuted in thread %lu, paran=%d\n", pthread_self(), *tt); free(t); }
int* change_pdata() { int *ptr; ptr=(int*)pthread_getspecific(key); return ptr; }
void *child1(void *arg) { pthread_t tid=pthread_self(); printf("thread1 %lu enter\n", tid); pthread_setspecific(key, malloc(sizeof(int)));
*change_pdata()=100;
sleep(1);
printf("thread1 %lu -->private data:%d\n", tid, *(int*)pthread_getspecific(key)); sleep(5); }
void *child2(void *arg) { pthread_t tid=pthread_self(); printf("thread2 %lu enter\n", tid); pthread_setspecific(key, malloc(sizeof(int)));
*change_pdata()=200;
sleep(2); printf("thread2 %lu -->private data:%d\n", tid, *(int*)pthread_getspecific(key)); sleep(5); }
int main(int argc, char *argv[]) { pthread_t tid1, tid2;
pthread_key_create(&key, &echomsg);
pthread_create(&tid1, NULL, &child1, NULL); pthread_create(&tid2, NULL, &child2, NULL);
sleep(10);
pthread_key_delete(key);
printf("main thread exit\n");
return 0; } |
thread1 3076152128 enter thread2 3067759424 enter thread1 3076152128 àprivate data:100 thread2 3067759424 àprivate data:200 destructor excuted in thread 3076152128, paran=100 destructor excuted in thread 3067759424, paran=200 main thread exit |
注意析构函数中不能调用pthread_getspecific函数获取私有数据指针,会出现内存错误。幸好在线程退出时将会将key所关联的数据为参数传入析构函数。