2011年(17)
分类: LINUX
2011-12-05 19:47:02
说到per-thread storage,先要谈谈函数的线程安全thread-safety。通常我们把一个能被多个线程同时访问,且函数输出数据不会因为线程访问顺序的不同而不同的函数叫做线程安全的函数。
在线程中调用线程不安全的函数时,必须要加锁或串行的执行。否则,会引起不可预料的错误(比如,两个线程同时在调用不可重入的malloc(),导致malloc()的内部全局管理变量混乱,造成内存泄露)。
在Linux中下表列出的函数都不是线程安全的,在线程中使用时,要注意!
为了让函数是可重入的reentrant,我们必须避免使用全局或静态变量,所有的信息都应该返回给调用者,调用者使用一个buffer来管理这些信息。在这里,我们不把使用互斥量,保证该函数被串行的执行的函数叫做可重入函数。但是,不是所有的函数都可以写成可重入的,原因如下:
1.有些函数必须使用全局数据,比如malloc(),该函数维护了一个全局的链表。该函数使用互斥量来保证线程安全。
2.有些函数的接口被定义成了非重入的。在函数内部它们动态分配了一个空间,并且使调用者的指针指向这个空间。上图中的很多函数都属于该类,不过在SUSv3中指定了一些上表中可重入的版本(用_r来区别)。asctime_r(), ctime_r()等。
所有新出现的库函数都应尽量设计成可重入的。但是,要把一些已经存在的非重入的库函数变成可重入的,需要改变它们的接口设计,这就意味着原来很多依赖这些库函数的程序不得不做出很大的改变,这根本就是不可能的(程序数目太多了)!线程特定数据解决了这个难题。
线程特定数据(Thread-Specific Data)
线程特定数据允许一个函数为每个线程都保留一份私有数据。
int pthread_key_create(pthread_key_t *key, void (*destructor)(void *))
注意点:
1.key会被同一个进程中的所有线程使用,所以它必须是全局变量。
2.key只能被创建一次(使用pthread_once()),为某个函数使用。
3.该函数只是在一个全局数组中找到一个未被使用空槽,把该空槽标志设置为“使用”,销毁函数指针指向destructor,然后返回该下标。它并没有为线程特定数据分配空间(分配空间是发生在pthread_setspecific()中)。
4.当线程终止时,销毁函数会自动被调用,把pthread_setspecific()中设置的value传递给它。
使用下面两个函数来设置或获得当前进程key对应的值
int pthread_setspecific(pthread_key_t key, const void *value)
void *pthread_getspecific(pthread_key_t key)
各函数和内部数据之间的关系
ThreadA是线程自己内部的数组,它和保存key的全局数组是一一对应的关系。当某个函数调用pthread_key_create()获得了全局数组槽位1时,在线程中也获得了内部数组的槽位1。(查看原代码可获得更详细的内容)
如何使用(省略了返回值的判断)
线程本地存储(Thread-Local Storage)
线程本地存储提供了另一种per-thread storage的机制。虽然这个特性非标准,但是它却在很多类UNIX系统上实现了。
它最大的优点是比线程特定数据thread-specific data使用简单。
创建一个线程本地变量,我们只需要像下面这样声明:
static __thread buf[100];
这样声明后,每个线程都有了buf’的一份拷贝。如果线程终止,该拷贝也会被销毁。