可重入函数reentrant function

来源:互联网 发布:梦幻西游2mac 编辑:程序博客网 时间:2024/06/07 01:28

在哪些情况下是需要考虑函数的可重入性的

1. 进程捕捉信号并对其进行处理时

进程捕获到信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。如果从信号处理程序返回(即在信号处理函数中没有调用exitlongjump),则继续执行在捕获到信号时进程正在执行的正常指令序列(这类似于发生硬件中断时所做的)。
但是在信号处理程序中,不能够判断捕捉到信号时进程执行到何处,则存在非常多的可能情况,以下列举了两种可能的情形。
情形一:

如果进程正在执行malloc,在其中分配另外的存储空间,而此时由于捕捉到信号而转去执行该信号的处理程序,在信号处理程序中又调用了malloc,这是会发生什么呢??

在这种情况下,因为malloc函数通常会为它所分配的存储区维护一个链表,而插入转去执行信号处理程序时,进程可能正在更改此链表,这就可能对进程造成破坏。

情形二:

进程正在执行getpwnam这种将其结果存放在静态存储单元中的函数,其间有转去执行信号处理程序,在信号处理程序中又调用了这个函数,这时又会发生什么呢?

显然,在这种情况下,返回给正常调用中的信息会被返回给信号处理程序的信息覆盖
因此,在信号处理程序中,要调用异步信号安全(async-signal safe)的,可重入(reentrant )的函数。Single Unix Specificaction说明了这些函数。

2. 多线程

关于多线程,可以参考下面这篇文章:

关于多线程简单原理

简单来说,OS的调度是随机的,因此在线程执行过程中的任意时刻都有可能被阻塞,然后CPU转去执行另外一个函数。因此这就会出现和上述相同的问题。

可重入性的本质

重入即表示重复进入,首先它意味着这个函数可以被中断(正如上面的转去执行信号处理程序,OS调度,CPU转去执行另一个线程的函数),其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。

不可重入函数

基于上面关于可重入性的讨论,我们可以总结出如下规则,满足如下条件之一的函数,不是可重入函数。
1. 它们调用了malloc或free
2. 它们使用标准I/O函数。标准I/O库的很多实现都以不可重入的方式使用了全局数据结构.
3. 函数体内访问全局变量
4. 已知它们使用静态数据结构

  • 函数的返回值是指向静态变量指针

    在我的这篇博文中讨论了这个问题:
    函数返回指针类型与函数的可重入性

  • 函数将其执行结果存储在静态存储单元中

    在第一节中描述的getpwnam就属于此类。

线程安全与可重入函数

在第一节中提到过,在多线程环境中是需要考虑函数的可重入性的。现在对这个问题进行详细地描述。这部分内容主要参考了如下博文:

线程安全与可重入函数
可重入函数与不可重入函数

线程安全(thread-safe)

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

根据上面的论述,我们可以确定可重入函数和线程安全函数之间的关系:

可重入函数是线程安全函数的一种,其特点在于它够被多个线程调用时,不会引用任何共享数据。

显然,我们在上面描述的不可重入函数都是线程不安全函数,如果在多线程环境中,不采用如何如何任何措施就使用不可重入函数,将会给程序带来不良的后果,有时甚至会导致程序崩溃,下面就描述可供使用的措施。

方法一:使用同步措施来保护共享变量

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

示例一:保护全局变量

假设Exam是int型全局变量,函数Squre_Exam返回Exam平方值。那么如下函数不具有可重入性。

unsigned int example( int para ){        unsigned int temp;        Exam = para; // (**)        temp = Square_Exam( );        return temp;}

此函数若被多个进程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另外一个使用本函数的线程可能正好被激活,那么当新激活的进程执行到此函数时,将使Exam赋与另一个不同的para值,所以当控制重新回到“temp = Square_Exam( )”后,计算出的temp很可能不是预想中的结果。此函数应如下改进。

    unsigned int example( int para ) {        unsigned int temp;        [申请信号量操作] //(1)        Exam = para;        temp = Square_Exam( );        [释放信号量操作]        return temp;    }

若申请不到“信号量”,说明另外的进程正处于给Exam赋值并计算其平方过程中(即正在使用此信号),本进程必须等待其释放信号后,才可继续执行。若申请到信号,则可继续执行,但其它进程必须等待本进程释放信号量后,才能再使用本信号。

保证函数的可重入性的方法:
在写函数时候尽量使用局部变量(例如寄存器、堆栈中的变量),对于要使用的全局变量要加以保护(如采取关中断、信号量等方法),这样构成的函数就一定是一个可重入的函数。
VxWorks中采取的可重入的技术有:
* 动态堆栈变量(各子函数有自己独立的堆栈空间)
* 受保护的全局变量和静态变量
* 任务变量

实例二:保护静态变量

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

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

如果线程不安全函数是难以修改或不可修改的(例如,它是从一个库中链接过来的),那么另外一种选择就是使用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;//不能够使用浅拷贝,必须使用深拷贝    memcpy(unsharedp, shared, sizeof(struct hostent));    V(&mutex);    return unsharedp;}

memcpy函数的文档:
memcpy

方法二:消除共享变量

不可重入版本:

unsigned int next = 1; int rand(void){     next = next * 1103515245 + 12345;     return (unsigned int) (next / 65536) % 32768;}

消除共享变量后的可重入版本:

int rand_r(unsigned int* nextp){     *nextp = *nextp * 1103515245 + 12345;      return (unsigned int) (*nextp / 65536) % 32768;}

如下列举了一些可重入函数与不可重入函数:
可重入函数

 void strcpy(char *lpszDest, char *lpszSrc) {        while(*lpszDest++=*lpszSrc++);        *dest=0; }

不可重入函数

 charcTemp;//全局变量 void SwapChar1(char *lpcX, char *lpcY) {        cTemp=*lpcX;        *lpcX=*lpcY;        lpcY=cTemp;//访问了全局变量  }

不可重入函数2

 void SwapChar2(char *lpcX,char *lpcY) {        static char cTemp;//静态局部变量        cTemp=*lpcX;        *lpcX=*lpcY;        lpcY=cTemp;//使用了静态局部变量  }
0 0