什么是可重入函数可重入的概念
若一个程序或子程序可以“在任意时刻被中断,然后操作系统调度执行另外一段代码,这段代码又调用了该子程序不会出错”,则称其为可重入(reentrant或re-entrant)的。
也就是说,当该子程序正在运行时,执行线程可以再度步入并执行它,仍然获得符合设计时预期的结果。与多线程并发执行的线程安全不同,可重入指出对单个线程执行时,重新步入同一个子程序,仍然是安全的。
可重入函数可以简单地理解为: 函数可以在任意时刻被中断并再度被调用(即重入),稍后再继续执行被中断的那次调用,而不会遗失数据。
不可重入
为易于理解可重入的概念,先来考察一个不可重入的事例:
我们现今有一个数组,insert()函数可向数组中插入一个结点
#3:8:6:8:6:6:3:b:7:9:a:c:1:8:b:e:7:9:4:9:2:1:c:c:6:0:6:9:d:e:7:a#
不可重入函数示例
在正在调用insert()函数插入结点node1时(假设此时刚完成node1的表针指向,图中1所示),被sighandler()函数中断,并在sighandler()函数中再度调用insert()函数向数组插入node2,完成后,此时head指向了node2,继续进行被中断的插入动作(即继续插入node1),则完成该动作以后head将指向node1,这样就造成中间插入的node2遗失。
因此,函数insert()是不可重入的。
可重入函数的条件
若一个函数是可重入的,则该函数应该满足下列条件:
上述条件就是要求可重入函数使用的所有变量都保存在调用栈的当前函数帧(frame)上。因此,同一执行线程重入执行该函数时 加载了新的函数帧,与前一次执行该函数时使用的函数帧不冲突、不相互覆盖,从而保证了可重入执行安全。
多“用户/对象/进程优先级”以及多进程,一般会促使对可重入代码的控制显得复杂。同时,IO代码一般不是可重入的,因为她们依赖于像c盘这样共享的、单独的资源(类似编程中的静态Static、全域Global资源)。
因此,如果函数体内调用了标准I/O函数,则该函数是不可重入的。
可重入出现的背景
可重入概念是在单线程操作系统的时代提出的。
这也促使可重入函数未必是线程安全的;线程安全函数未必是可重入的。
一个子程序的重入,可能因为自身缘由,如执行了jmp或则call,类似于子程序的递归调用;或者因为操作系统的中断响应。UNIX系统的signal的处理,即子程序被中断处理程序或则signal处理程序调用。所以,可重入也可叫做“异步讯号安全”。这里的异步是指讯号中断可发生在任意时刻。 重入的子程序,按照后进先出线性序依次执行。
示例
下面两个函数f1()和f2()都不是可重入函数:
#a:8:7:5:c:3:9:b:c:5:f:8:7:7:f:5:2:b:e:7:e:b:f:0:3:a:6:d:2:c:5:a#
原因剖析
函数f1()使用了全局变量global_var,所以 如果两个或多个线程同时执行它并访问global_var,则返回的结果取决于执行的时间。因此,f1()不可重入。而函数f2()调用了f1(),所以它也不可重入。
修改为可重入函数
不再使用全局变量,而是改为以参数的方式接收输入,则两个函数都是可重入的:
#6:b:e:0:b:1:e:8:2:8:5:6:1:a:4:4:0:2:d:7:3:9:9:d:2:7:b:a:8:a:8:2#
Linux提供的可重入函数
#e:6:5:b:e:b:7:7:1:5:b:b:1:8:a:2:2:8:2:8:d:e:a:e:2:0:c:b:e:d:4:f#
声明:本文部份整理自维基百科:可重入,本文所有文字遵照知识共享 署名-相同形式共享 3.0合同