可重入函数
1. 概念
重入函数: 函数被不同的控制流调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入函数。
可重入函数:不同的控制流重入函数时,不会造成数据二义性。具体来说就是函数内部的数据都应该来自自身的栈空间,不应该有全局或者静态的变量。
不可重入函数:重入函数会造成数据的二义性,导致对一个变量的操作变得无法预期。称为不可重入函数。
接下来我们简单的分别举上一个可重入函数和不可重入函数的例子。
2.示例
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
int count=0;
void fun()
{
int i=5;
while(i--)
{
sleep(1);
count++;
printf("count:%d\n",count);
}
}
int main()
{
signal(2,fun);
fun();
printf("count:%d\n",count);
return 0;
}
分析上面的代码和执行结果,fun函数里对全局变量进行++操作。主函数里会捕捉2号信号(即ctrl+c)信号,捕捉后执行自定义函数fun。同时主函数里也调用了fun函数。所以,当我们运行test后,在主函数调用fun函数还没有完成时,从键盘输入ctrl+c产生2号信号,程序从内核态切回用户态之前会检查有无信号需要处理,发现有了一个2号信号,便先去处理2号信号,对它的处理方式是自定义函数fun,于是重入fun函数,执行++。信号处理完毕,从内核态转回用户态,继续执行刚才还没有完成的主函数对于fun函数的调用。所以,本来应该加到5的count,最后加到了10。造成了数据的二义性,这样的函数称为不可重入函数。
我们把上面的代码改一下,将count变量设置为局部变量,再次运行。
这样,两个执行流并没有互相影响,这时的fun函数称为可重入函数。
3.总结
一个可重入函数需要满足以下几个要求:
1、不使用全局变量或静态变量;
2、不使用用malloc或者new开辟出的空间;(malloc使用的是全局的链表来维护堆)
3、不调用不可重入函数;
4、不返回静态或全局数据,所有数据都由函数的调用者提供;
5、一定要用全局数据的话可以在函数内部进行全局的局部拷贝;
线程安全
1.概念
线程安全:多个线程访问同一个区域的时候其最终结果是可预期的,并不会因为产生冲突或者异常中断再次恢复而使结果不可预期。
例如上一篇博客我在模拟生产者消费者模型时,使用了信号量来保证线程的安全。如果一段会被多个线程访问的代码操作本身并不是原子的,那我们就必须利用互斥锁或信号量等对该段代码进行保护,避免产生数据二义性的问题。一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生正确的结果。
2.可重入函数与线程安全的区别和联系
1、线程安全是在多线程情况下引发的,而可重入函数可以在只有一个线程的情况下发生。
2、线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
(例如:对临界资源的访问加锁,使函数是线程安全的,但如果重入函数时加锁还未释放,则会产生死锁,因此不能重入。)
3、如果一个函数有全局变量,则这个函数既不是线程安全也不是可重入的。
4、如果一个函数当中的数据全身自身栈空间的,则这个函数即是线程安全也是可重入的。
5、线程安全函数能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作不影响结果,使结果是相同的。