线程安全

线程安全与每数据存储

1.线程安全

若函数可以同时供多个线程同时安全调用,暗恶魔称之为线程安全函数,如果函数不是线程安全,则不能够并发调用

  • 实现线程安全的方式

    1. 将函数与互斥量关联使用,在调用函数时将其锁定,在函数返回时解锁

      该方法的优点在于简单,但是这也就意味着同时只能有一个线程执行该函数,如果各线程在执行此函数时耗费了相当多的时间,那么该执行方式就相当于串行了,导致并发能力的丧失

    2. 将共享变量与互斥量关联起来

      即在函数即将进入临界区时去获得和释放互斥量,这允许多个线程去并发执行一个函数

  • 非线程安全的函数

    以下所列函数均为非线程安全(至少不应该把他们当做是)


    非线程安全函数.png
  • 可重入函数与不可重入函数

    可重入函数无需使用互斥量即可保证线程安全,关键在于避免对全局变量和静态变量的使用,需要返回给调用者的信息,应该都存储于由调用者分配的缓冲区

    对于一些接口不可重入的函数,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 失败:返回一个正数
    

    参数介绍:

    1. key

      在分配(malloc)线程的私有数据之前,需要创建和线程私有数据相关联的键(key),这个键的功能是获得对线程私有数据的访问权

    2. 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 失败:正数
    

    参数:

    1. key

      待注销的私有数据

  • pthread_setspecific

    该函数会设置线程的私有数据(key)与value关联(而非与其所指的内容关联)

    #include<pthread.h>
    int pthread_setspecific(pthread_key_t key, const void *value);
                                              //成功: 0 失败:正数
    

    参数:

    1. key

      线程的私有数据

    2. 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

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容