UNIX环境高级编程——线程私有数据
线程私有数据(Thread-specific data,TSD):存储和查询与某个线程相关数据的一种机制。
- 在进程内的所有线程都共享相同的地址空间,即意味着任何声明为静态或外部变量,或在进程堆声明的变量,都可以被进程内所有的线程读写。
- 一个线程真正拥有的唯一私有存储是处理器寄存器,栈在“主人”故意暴露给其他线程时也是共享的。
- 有时需要提供线程私有数据:可以跨多个函数访问(全局);仅在某个线程有效(私有)(即在线程里面是全局)。例如:errno。
进程中的所有线程都可以访问进程的整个地址空间,除非使用寄存器(一个线程真正拥有的唯一私有存储是处理器寄存器),线程没有办法阻止其它线程访问它的数据,线程私有数据也不例外,但是管理线程私有数据的函数可以提高线程间的数据独立性。
进程内的所有线程共享进程的数据空间,因此全局变量为所有线程所共有。但有时线程也需要保存自己的私有数据,这时可以创建线程私有数据(Thread-specific Date)TSD来解决。在线程内部,私有数据可以被各个函数访问,但对其他线程是屏蔽的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量。(即在线程里面是全局变量)
创建线程私有数据就是为了线程内部各个函数可以很容易的传递数据信息,因此要使线程外的函数不能访问这些数据,而线程内的函数使用这些数据就像线程内的全局变量一样,这些数据在一个线程内部是全局的,一般用线程私有数据的地址作为线程内各个函数访问该数据的入口。
线程私有数据采用了一种被称为一键多值的技术,即一个键对应多个数值。访问数据时都是通过键值来访问,好像是对一个变量进行访问,其实是在访问不同的数据。使用线程私有数据时,首先要为每个线程私有数据创建一个相关联的键。在各个线程内部,都使用这个公用的键来指代线程数据,但是在不同的线程中,这个键代表的数据是不同的。操作线程私有数据的函数主要有4个:pthread_key_create(创建一个键),pthread_setspecific(为一个键设置线程私有数据),pthread_getspecific(从一个键读取线程私有数据),pthread_key_delete(删除一个键)。
创建一个键:
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));//返回值:若成功则返回0,否则返回错误编号在分配(malloc)线程私有数据之前,需要创建和线程私有数据相关联的键(key),这个键的功能是获得对线程私有数据的访问权。
如果创建一个线程私有数据键,必须保证pthread_key_create对于每个Pthread_key_t变量仅仅被调用一次,因为如果一个键被创建两次,其实是在创建两个不同的键,第二个键将覆盖第一个键,第一个键以及任何线程可能为其关联的线程私有数据值将丢失。
创建新键时,每个线程的私有数据地址设为NULL。
注意:创建的键存放在keyp指向的内存单元,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程私有数据地址进行关联。
除了创建键以外,pthread_key_create可以选择为该键关联析构函数,当线程退出时,如果线程私有数据地址被置为非NULL值,那么析构函数就会被调用。
注意:析构函数参数为退出线程的私有数据的地址。如果私有数据的地址为NULL,就说明没有析构函数与键关联即不需要调用该析构函数。
当线程调用pthread_exit或者线程执行返回,正常退出时,析构函数就会被调用,但是如果线程调用了exit、_exit、Exit函数或者abort或者其它非正常退出时,就不会调用析构函数。
线程通常使用malloc为线程私有数据分配空间,析构函数通常释放已分配的线程私有数据的内存。
线程可以为线程私有数据分配多个键,每个键都可以有一个析构函数与它关联。各个键的析构函数可以互不相同,当然它们也可以使用相同的析构函数。
线程退出时,线程私有数据的析构函数将按照操作系统实现定义的顺序被调用。析构函数可能调用另外一个函数,而该函数可能创建新的线程私有数据而且把这个线程私有数据和当前的键关联起来。当所有的析构函数都调用完成以后,系统会检查是否有非NULL的线程私有数据值与键关联,如果有的话,再次调用析构函数,这个过程一直重复到线程所有的键都为NULL值线程私有数据,或者已经做了PTHREAD_DESTRUCTOR_ITERATIONS中定义的最大次数的尝试。
取消键与线程私有数据之间的关联:
int pthread_delete(pthread_key_t *keyp);//返回值:若成功则返回0,否则返回错误编号注意调用pthread_delete不会激活与键关联的析构函数。删除线程私有数据键的时候,不会影响任何线程对该键设置的线程私有数据值,甚至不影响调用线程当前键值,所以容易造成内存泄露(因为键不与私有数据关联了,当线程正常退出的时候不会调用键的析构函数,最终导致线程的私有数据这块内存没有释放)。使用已经删除的私有数据键将导致未定义的行为。
注意:对于每个pthread_key_t变量(即键)必须仅调用一次pthread_key_create。如果一个键创建两次,其实是在创建不同的键,第二个键将覆盖第一个,第一个键与任何线程可能为其设置的值将一起永远的丢失。所以,pthread_key_create放在主函数中执行;或每个线程使用pthread_once来创建键。
线程私有数据与键关联:
int pthread_setspecific(pthread_key_t key,const void *value);//返回值:若成功则返回0,否则返回错误编号 void* pthread_getspecific(pthread_key_t key);//返回值:线程私有数据地址;若没有值与键关联则返回NULL如果没有线程私有数据值与键关联,pthread_getspecific键返回NULL,可以依据此来确定是否调用pthread_setspecific。
注意:两个线程对自己的私有数据操作是互相不影响的。也就是说,虽然 key 是同名且全局,但访问的内存空间并不是相同的一个。key 就像是一个数据管理员,线程的私有数据只是到他那去注册,让它知道你这个数据的存在。
#include <stdio.h> #include <pthread.h> #include <stdlib.h> typedef struct private_tag { pthread_t thread_id; char *string; } private_t; pthread_key_t identity_key; /* Thread-specific data key */ pthread_mutex_t identity_key_mutex = PTHREAD_MUTEX_INITIALIZER; long identity_key_counter = 0; void identity_key_destructor (void *value) { private_t *private = (private_t*)value; int status; printf ("thread \"%s\" exiting...\n", private->string); free (value); status = pthread_mutex_lock (&identity_key_mutex); if (status != 0) perror("pthread_mutex_lock"); identity_key_counter--; if (identity_key_counter <= 0) { status = pthread_key_delete (identity_key); if (status != 0) perror("pthread_key_delete"); printf ("key deleted...\n"); } status = pthread_mutex_unlock (&identity_key_mutex); if (status != 0) perror("pthread_mutex_unlock"); } void *identity_key_get (void) { void *value; int status; value = pthread_getspecific (identity_key); if (value == NULL) { value = malloc (sizeof (private_t)); if (value == NULL) perror ("malloc"); status = pthread_setspecific (identity_key, (void*)value); if (status != 0) perror("pthread_setspecific"); } return value; } void *thread_routine (void *arg) { private_t *value; value = (private_t*)identity_key_get (); value->thread_id = pthread_self (); value->string = (char*)arg; printf ("thread \"%s\" starting...\n", value->string); sleep (2); return NULL; } void main (int argc, char *argv[]) { pthread_t thread_1, thread_2; private_t *value; int status; status = pthread_key_create (&identity_key, identity_key_destructor); if (status != 0) perror("pthread_key_create"); identity_key_counter = 3; value = (private_t*)identity_key_get (); value->thread_id = pthread_self (); value->string = "Main thread"; status = pthread_create (&thread_1, NULL,thread_routine, "Thread 1"); if (status != 0) perror("pthread_create"); status = pthread_create (&thread_2, NULL,thread_routine, "Thread 2"); if (status != 0) perror("pthread_create"); pthread_exit (NULL); }运行结果:
huangcheng@ubuntu:~$ ./a.out thread "Main thread" exiting... thread "Thread 2" starting... thread "Thread 1" starting... thread "Thread 2" exiting... thread "Thread 1" exiting... key deleted... huangcheng@ubuntu:~$
示例代码2:
#include <stdio.h> #include <pthread.h> #include <stdlib.h> typedef struct tsd_tag{ pthread_t thread_id; char *string; }tsd_t; pthread_key_t key; pthread_once_t once = PTHREAD_ONCE_INIT; void once_routine(void) { int status; printf("Initializing key\n"); status = pthread_key_create(&key, NULL); if(status != 0){ perror("pthread_key_create"); } } void *thread_routine(void *arg) { int status; tsd_t *value = NULL; status = pthread_once(&once, once_routine); if(status != 0){ perror("pthread_once"); } value = (tsd_t *)malloc(sizeof(tsd_t)); if(value == NULL){ perror("malloc"); } status = pthread_setspecific(key, (void *)value); if(status != 0){ perror("pthread_setspecific"); } printf("%s set tsd value at %p\n", (char *)arg, value); value->thread_id = pthread_self(); value->string = (char *)arg; printf("%s starting......\n", (char *)arg); sleep(2); value = (tsd_t *)pthread_getspecific(key); if(value == NULL){ printf("no thread-specific data value was associated \ with key\n"); pthread_exit(NULL); } printf("%s done......\n", value->string); } int main(int argc, char **argv) { pthread_t thread1, thread2; int status; status = pthread_create(&thread1, NULL, thread_routine, "thread 1"); if(status != 0){ perror("pthread_create"); } status = pthread_create(&thread2, NULL, thread_routine, "thread 2"); if(status != 0){ perror("pthread_create"); } pthread_exit(NULL); }
运行结果:
huangcheng@ubuntu:~$ ./a.out Initializing key thread 2 set tsd value at 0x8fb7520 thread 2 starting...... thread 1 set tsd value at 0x8fb7530 thread 1 starting...... thread 2 done...... thread 1 done......
示例代码3:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> pthread_key_t key; struct test_struct { int i; float k; }; void *child1 (void *arg) { struct test_struct struct_data; struct_data.i = 10; struct_data.k = 3.1415; pthread_setspecific (key, &struct_data); printf ("结构体struct_data的地址为 0x%p\n", &(struct_data)); printf ("child1 中 pthread_getspecific(key)返回的指针为:0x%p\n", (struct test_struct *)pthread_getspecific(key)); printf ("利用 pthread_getspecific(key)打印 child1 线程中与key关联的结构体中成员值:\nstruct_data.i:%d\nstruct_data.k: %f\n", ((struct test_struct *)pthread_getspecific (key))->i, ((struct test_struct *)pthread_getspecific(key))->k); printf ("------------------------------------------------------\n"); } void *child2 (void *arg) { int temp = 20; sleep (2); printf ("child2 中变量 temp 的地址为 0x%p\n", &temp); pthread_setspecific (key, &temp); printf ("child2 中 pthread_getspecific(key)返回的指针为:0x%p\n", (int *)pthread_getspecific(key)); printf ("利用 pthread_getspecific(key)打印 child2 线程中与key关联的整型变量temp 值:%d\n", *((int *)pthread_getspecific(key))); } int main (void) { pthread_t tid1, tid2; pthread_key_create (&key, NULL); pthread_create (&tid1, NULL, (void *)child1, NULL); pthread_create (&tid2, NULL, (void *)child2, NULL); pthread_join (tid1, NULL); pthread_join (tid2, NULL); pthread_key_delete (key); return (0); }
运行结果:
huangcheng@ubuntu:~$ ./a.out 结构体struct_data的地址为 0x0xb77db388 child1 中 pthread_getspecific(key)返回的指针为:0x0xb77db388 利用 pthread_getspecific(key)打印 child1 线程中与key关联的结构体中成员值: struct_data.i:10 struct_data.k: 3.141500 ------------------------------------------------------ child2 中变量 temp 的地址为 0x0xb6fda38c child2 中 pthread_getspecific(key)返回的指针为:0x0xb6fda38c 利用 pthread_getspecific(key)打印 child2 线程中与key关联的整型变量temp 值:20