线程私有数据(thread_specific_data)详解

  进程内的所有线程共享进程的数据空间,所以全局变量为所有线程共有。在某些场景下,线程需要保存自己的私有数据,这时可以创建线程私有数据(Thread-specific Data)TSD来解决。在线程内部,私有数据可以被线程的各个接口访问,但对其他线程屏蔽。

  TSD采用了一种被称为一键多值的技术,即一个键对应多个数值。访问数据时都是通过键值来访问,好像是对一个变量进行访问,其实是在访问不同的数据。使用线程私有数据时,首先要为每个线程数据创建一个相关联的键。在各个线程内部,都使用这个公用的键来指代线程数据,但是在不同的线程中,这个键代表的数据是不同的。

  POSIX中操作线程私有数据的主要通过以下4个接口来实现:

  • pthread_key_create:创建一个键
1 int pthread_key_create(pthread_key_t *key, void (*destr_function) (void*));

  首先从linux的TSD池中分配一项,然后将其值赋给key供以后访问使用。接口的第一个参数是指向键的指针,第二参数是函数指针,如果该指针不为空,那么在线程执行完毕退出时,以key指向的内容为参数调用destr_function(),释放分配的缓冲区以及其他数据。destr_function非常便利,即使线程执行过程中由于被cancel而非正常终止,该函数也会被调用,如果key中的值为NULL,该函数不会被调用,但是如果线程调用了exit系列函数或者abort或者其它非正常退出时,就不会调用析构函数。

  key被创建之后,因为是全局变量,所以所有的线程都可以访问。各个线程可以根据需求向key中填入不同的值,这就相当于提供了一个同名而值不同的全局变量,即一键多值。一键多值依靠的是一个结构体数组,即

1 static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] ={{0,NULL}};

  其中pthread_key_struct 的定义为:

 1 struct pthread_key_struct
 2 {
 3   /* Sequence numbers.  Even numbers indicated vacant entries.  Note
 4      that zero is even.  We use uintptr_t to not require padding on
 5      32- and 64-bit machines.  On 64-bit machines it helps to avoid
 6      wrapping, too.  */
 7   uintptr_t seq;
 8 
 9   /* Destructor for the data.  */
10   void (*destr) (void *);
11 };

  PTHREAD_KEYS_MAX值为1024

  创建一个TSD,相当于将结构体数组的某一个元素的seq值设置为为“in_use”,并将其索引返回给*key,然后设置destr_function()为destr()。pthread_key_create创建一个新的线程私有数据key时,系统会搜索其所在进程的key结构数组,找出一个未使用的元素,将其索引赋给*key。

  • pthread_setspecific:为指定键值设置线程私有数据
1 int pthread_setspecific(pthread_key_t key, const void *pointer);

  该接口将指针pointer的值(指针值而非其指向的内容)与key相关联,用pthread_setspecific为一个键指定新的线程数据时,线程必须释放原有的数据用以回收空间。

  • pthread_getspecific:从指定键读取线程的私有数据
1 void * pthread_getspecific(pthread_key_t key);
  • pthread_key_delete:删除一个键
1 int pthread_key_delete(pthread_key_t key);

  该接口用来删除一个键,键所占用的内存将被释放。需要注意的是,键占用的内存被释放,与该键关联的线程数据所占用的内存并不被释放。因此,线程数据的释放必须在释放键之前完成。

  编程建议:最后不删除线程私有数据键!!!尤其当一些线程仍然持有该键的值时,就更不该释放该键!!!

 

  如果创建一个线程私有数据键,必须保证pthread_key_create对于每个pthread_key_t变量仅仅被调用一次,因为如果一个键被创建两次,其实是在创建两个不同的键,第二个键将覆盖第一个键,第一个键以及任何线程可能为其关联的线程私有数据值将丢失。

  一次性初始化:  

  在传统的顺序编程中,一次性初始化经常通过使用布尔变量来管理。控制变量被静态初始化为0,而任何依赖于初始化的代码都测试该变量。如果变量值仍然为0,则它能实行初始化,然后将变量置为1。以后检查的代码将跳过初始化。

    但是在多线程程序设计中,事情就变的复杂的多。如果多个线程并发地执行初始化序列代码,可能有2个线程发现控制变量为0,并且都实行初始化,而该过程本该仅仅执行一次。

  如果我们需要对一个POSIX变量静态的初始化,可使用的方法是用一个互斥量对该变量的初始化进行控制。但有时候我们需要对该变量进行动态初始化,pthread_once就会方便的多。

  函数原形:

  pthread_once_t once_control=PTHREAD_ONCE_INIT;

  int pthread_once(pthread_once_t *once_control,void(*init_routine)(void));

  参数:

  once_control         控制变量

  init_routine         初始化函数

  返回值:

  若成功返回0,若失败返回错误编号。

  类型为pthread_once_t的变量是一个控制变量。控制变量必须使用PTHREAD_ONCE_INIT宏静态地初始化。

  pthread_once函数首先检查控制变量,判断是否已经完成初始化,如果完成就简单地返回;否则,pthread_once调用初始化函数,并且记录下初始化被完成。如果在一个线程初始化时,另外的线程调用pthread_once,则调用线程等待,直到那个线程完成初始化返回。

  多线程下容易重复初始化的代码段:

 1 void destructor(void *);
 2 
 3 pthread_key_t    key;
 4 int    flag;
 5 
 6 int threadfun(void *arg)
 7 {
 8     if(!flag)
 9     {
10         flag = 1;
11         pthread_key_create(&key, destructor);
12     }
13 }    

  使用pthread_once:

 1 void destructor(void *);
 2 
 3 pthread_once_t initonce = PTHREAD_ONCE_INIT;
 4 pthread_key_t    key;
 5 
 6 void thread_once(void)
 7 {
 8     pthread_key_create(&key, destructor);
 9 }
10 
11 int threadfun(void *arg)
12 {
13     pthread_once(&initonce, thread_once);
14     ...
15 }

 

  thread_specific_data使用的简单示例如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <pthread.h>
 4 
 5 
 6 static pthread_key_t key;
 7 
 8 pthread_once_t once = PTHREAD_ONCE_INIT;
 9 
10 void once_run (void)
11 {
12     int rtn = 0;
13     static int times = 1;
14 
15     rtn = pthread_key_create(&key, NULL);
16     {
17     printf("key create OK, by thread:%lu\n", pthread_self());
18     }
19 
20     printf("The %d times run\n", times);
21     times++;
22 }
23 
24 
25 void* thread_func (void* arg)
26 {
27     pthread_t tid = pthread_self();
28 
29     printf("thread %lu start running!\n", tid);
30     
31     pthread_once(&once, once_run);
32 
33     pthread_setspecific(key, (void*)tid);
34 
35     printf("thread id:%lu, key value:%lu\n", tid, (pthread_t)pthread_getspecific(key));
36 
37     printf("thread %lu quit!\n", tid);
38 
39     return 0;
40 }
41 
42 
43 int main ()
44 {
45     pthread_t tid1, tid2;
46     pthread_create(&tid1, NULL, thread_func, NULL);
47     pthread_create(&tid2, NULL, thread_func, NULL);
48     
49     pthread_join(tid1, NULL);
50     pthread_join(tid2, NULL);
51 
52     printf("main thread exit!\n");
53     return 0;
54 }

  运行结果如下:

 

posted @ 2017-04-05 21:18  河马噗噗噗  阅读(376)  评论(0)    收藏  举报