• 线程安全与可重入函数


    线程安全一个函数被称为线程安全的(thread-safe),当且仅当被多个并发进程反复调用时,它会一直产生正确的结果。如果一个函数不是线程安全的,我们就说它是线程不安全的(thread-unsafe)。我们定义四类(有相交的)线程不安全函数。

    第1类:不保护共享变量的函数

    将这类线程不安全函数变为线程安全的,相对比较容易:利用像P和V操作这样的同步操作来保护共享变量。这个方法的优点是在调用程序中不需要做任何修改,缺点是同步操作将减慢程序的执行时间。

    第2类:保持跨越多个调用的状态函数

    一个伪随机数生成器是这类不安全函数的简单例子。

    unsigned int next = 1;

    int rand(void)

    {

    next = next * 1103515245 + 12345;

    return (unsigned int) (next / 65536) % 32768;

    }

       

    void srand(unsigned int seed)

    {

    next = seed;

    }

    rand函数是线程不安全的,因为当前调用的结果依赖于前次调用的中间结果。当我们调用srand为rand设置了一个种子后,我们反复从一个单线程中调用rand,我们能够预期一个可重复的随机数字序列。但是,如果有多个线程同时调用rand函数,这样的假设就不成立了。

    使得rand函数变为线程安全的唯一方式是重写它,使得它不再使用任何静态数据,取而代之地依靠调用者在参数中传递状态信息。这样的缺点是,程序员现在要被迫改变调用程序的代码。

    第3类:返回指向静态变量指针的函数

    某些函数(如gethostbyname)将计算结果放在静态结构中,并返回一个指向这个结构的指针。如果我们从并发线程中调用这些函数,那么将可能发生灾难,因为正在被一个线程使用的结果会被另一个线程悄悄地覆盖了。

    有两种方法来处理这类线程不安全函数。一种是选择重写函数,使得调用者传递存放结果的结构地址。这就消除了所有共享数据,但是它要求程序员还要改写调用者的代码。

    如果线程不安全函数是难以修改或不可修改的(例如,它是从一个库中链接过来的),那么另外一种选择就是使用lock-and-copy(加锁-拷贝)技术。这个概念将线程不安全函数与互斥锁联系起来。在每个调用位置,对互斥锁加锁,调用函数不安全函数,动态地为结果非配存储器,拷贝函数返回的结果到这个存储器位置,然后对互斥锁解锁。一个吸引人的变化是定义了一个线程安全的封装(wrapper)函数,它执行lock-and-copy,然后调用这个封转函数来取代所有线程不安全的函数。例如下面的gethostbyname的线程安全函数。

    struct hostent* gethostbyname_ts(char* host)

    {

    struct hostent* shared, * unsharedp;

    unsharedp = Malloc(sizeof(struct hostent));

    P(&mutex)

    shared = gethostbyname(hostname);

    *unsharedp = * shared;

    V(&mutex);

    return unsharedp;

    }

    第4类:调用线程不安全函数的函数

    如果函数f调用线程不安全函数g,那么f就是线程不安全的吗?不一定。如果g是类2类函数,即依赖于跨越多次调用的状态,那么f也是不安全的,而且除了重写g以外,没有什么办法。然而如果g是第1类或者第3类函数,那么只要用互斥锁保护调用位置和任何得到的共享数据,f可能仍然是线程安全的。比如上面的gethostbyname_ts。

    可重入函数

    可重入函数:可重入函数是线程安全函数的一种,其特点在于它们被多个线程调用时,不会引用任何共享数据。
    可重入函数通常要比不可重入的线程安全函数效率高一些,因为它们不需要同步操作。更进一步说,将第2类线程不安全函数转化为线程安全函数的唯一方法就是重写它,使之可重入。我们可以简单的画出它们的关系

    下面为rand函数的一个可重入版本

    int rand_r(unsigned int* nextp)

    {

    *nextp = *nextp * 1103515245 + 12345;

    return (unsigned int) (*nextp / 65536) % 32768;

    }

    显式可重入函数:如果所有函数的参数都是传值传递的(没有指针),并且所有的数据引用都是本地的自动栈变量(也就是说没有引用静态或全局变量),那么函数就是显示可重入的,也就是说不管如何调用,我们都可断言它是可重入的。

    隐式可重入函数:可重入函数中的一些参数是引用传递(使用了指针),也就是说,在调用线程小心地传递指向非共享数据的指针时,它才是可重入的。例如rand_r就是隐式可重入的。
    我们使用可重入(reentrant)来包括显式可重入函数和隐式可重入函数。然而,可重入性有时是调用者和被调用者共有的属性,并不只是被调用者单独的属性。

     

    摘自:http://blog.xiaoheshang.info/?cat=11

  • 相关阅读:
    可能用到的两款Linux工具tmux和tpp
    CCF技术动态第68期(云计算专题)...中国云计算发展概况与趋势...
    产生随机数//NULL大写,rand()在cstdlib,不在cmath
    28个Unix/Linux的命令行神器
    oAuth无痛入门指南
    CRM 客户关系管理和云计算...Salesforce企业级云计算的领导者...
    博客终于开通了
    传递与引用
    Java基础概念
    final、finally和finalize(二)
  • 原文地址:https://www.cnblogs.com/MrListening/p/5843435.html
Copyright © 2020-2023  润新知