线程安全与每数据存储
1.线程安全
若函数可以同时供多个线程同时安全调用,暗恶魔称之为线程安全函数,如果函数不是线程安全,则不能够并发调用
-
实现线程安全的方式
-
将函数与互斥量关联使用,在调用函数时将其锁定,在函数返回时解锁
该方法的优点在于简单,但是这也就意味着同时只能有一个线程执行该函数,如果各线程在执行此函数时耗费了相当多的时间,那么该执行方式就相当于串行了,导致并发能力的丧失
-
将共享变量与互斥量关联起来
即在函数即将进入临界区时去获得和释放互斥量,这允许多个线程去并发执行一个函数
-
-
非线程安全的函数
以下所列函数均为非线程安全(至少不应该把他们当做是)
-
可重入函数与不可重入函数
可重入函数无需使用互斥量即可保证线程安全,关键在于避免对全局变量和静态变量的使用,需要返回给调用者的信息,应该都存储于由调用者分配的缓冲区
对于一些接口不可重入的函数,SUSv3为其定义了以后缀_r结尾的可重入"替身",这些替身函数要求由调用者来分配缓冲区,并且将缓冲区地址用于给函数用于返回结果
2.一次性初始化
-
pthread_once
当pthread_once()首次被任何线程所调用时,会执行初始化动作
#include<pthread.h> int pthread_once(pthread_once_t *once_control, void (*init)(void)); //成功:返回0, 失败:返回一个正数
init是一个调用者自定义的函数,格式如下
void init(void){ //... }
当使用pthread_once()函数时,会使用init()函数来进行初始化
once_control是一个指针,指向初始化为PTHREAD_ONCE_INIT的静态变量,即
static pthread_once_t once_var = PTHREAD_ONCE_INIT;
对该函数的首次调用将改变once_control的值,使得后续调用不会再次执行init
3.线程特有数据
在一个进程中定义的全局或静态变量都是所有线程可见的,即每个线程共同操作一块存储区域。而有时我们可能有这样的需求:对于一个全局变量,每个线程对其的修改只在本线程内有效,各线程之间互不干扰。即每个线程虽然共享这个全局变量的名字,但这个变量的值就像只有在本线程内才会被修改和读取一样
线程特有数据(TSD)使得函数的移位每个调用线程分别维护一份变量的副本,线程特有的数据是长期存在的,在同一个线程对相同函数的历次调用期间,每个线程的变量会持续存在,函数可以量每个调用线程返回各自的结果缓冲区
-
pthread_key_create
pthread_key_create会创建一个类型为pthread_key_t 类型的私有数据变量(key)
#include<pthread.h> int pthread_key_create(pthread_key_t *key, void (destructor)(void*)) //成功:返回0 失败:返回一个正数
参数介绍:
-
key
在分配(malloc)线程的私有数据之前,需要创建和线程私有数据相关联的键(key),这个键的功能是获得对线程私有数据的访问权
-
destructor
析构器函数,当线程退出时,如果线程私有数据地址不是非NULL,此函数会被自动调用,如果将该函数指针设置为NULL,那么系统将调用默认的清理函数
destructor的函数定义如下
void destructor(void *arg){ //... }
-
-
pthread_key_delete
注销线程的私有数据。这个函数并不会检查当前是否有线程正在使用线程私有数据(key),也不会调用清理函数destructor(),而只是将线程私有数据(key)释放一共下一次使用
#include<pthread.h> int pthread_key_delete(pthread_key_t key); //成功:0 失败:正数
参数:
-
key
待注销的私有数据
-
-
pthread_setspecific
该函数会设置线程的私有数据(key)与value关联(而非与其所指的内容关联)
#include<pthread.h> int pthread_setspecific(pthread_key_t key, const void *value); //成功: 0 失败:正数
参数:
-
key
线程的私有数据
-
value
与key相关联的指针
-
-
pthread_getspecific
读取线程私有数据(key)所关联的值
void* pthread_getspecific(pthread_key_t key); //成功:与key关联的value 失败:NULL
线程特有数据的实现
典型的实现方法会包含以下两个数组
-
一个全局数组pthread_keys(即整个进程只包含一个)
该数组存放线程特有数据(key)的信息,数组中的每个元素都包含两个字段,一个标记该数组元素是否在用,另一个则标记与key相关联的解构函数的地址
-
每个线程自身的一个数组
因为一个线程可以调用很多函数,所以一个线程就会拥有很多它在不同函数中所分配的数据块,该数组存放着该线程所持有的所有数据块的指针
这两个数组之间的关系如下图所示
在不同函数中调用pthread_key_create(),相当于只是在pthread_key数组增加了一个条目而已,其返回的key只是全局pthread_key数组中的一个索引
调用pthread_setspecific()时key与value是一一对应的关系,相应的操作就是在线程自身的数组中增加一个条目tsd[key],其内容为我们传递的value