• 线程安全与可重入函数


    线程安全:一个函数被称为线程安全的(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

  • 相关阅读:
    【动手学深度学习pytorch】学习笔记 8.1 序列模型
    神经网络与深度学习(邱锡鹏)编程练习6 RNN
    神经网络与深度学习(邱锡鹏)编程练习4 FNN 反向传播 梯度下降 numpy
    神经网络与深度学习(邱锡鹏)编程练习4 FNN 均方误差 numpy vs. pytorch
    扩展学习: 中文分词;词云制作
    神经网络与深度学习(邱锡鹏)编程练习4 FNN 正向传播 pytorch
    神经网络与深度学习(邱锡鹏)编程练习4 FNN 交叉熵二分类 numpy vs. pytorch
    【动手学深度学习pytorch】学习笔记 8.4 循环神经网络
    神经网络与深度学习(邱锡鹏)编程练习4 FNN 反向传播 梯度下降 pytorch
    神经网络与深度学习(邱锡鹏)编程练习5 CNN pytorch版
  • 原文地址:https://www.cnblogs.com/xiangshancuizhu/p/2734497.html
Copyright © 2020-2023  润新知