【Linux】线程安全和可重入函数


可重入函数和不可重入函数
     
        函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入。
        当程序运行到某一个函数的时候,可能因为硬件中断或者异常而使得在用户正在执行的代码暂时中断从而转向内核态,当内核处理完异常这个时候如有一个信号需要被处理,调用sighandler函数切换至用户态处理这个信号,而处理这个信号的时候又重新调用了刚才中断的函数,如果函数内部有一个全局变量需要被操作,那么,当信号处理完成之后重新返回用户态恢复中断函数的上下文再次继续执行的时候,对同一个全局变量的操作结果可能就会发生改变而并不如我们预期的那样,这样的函数被称为不可重入函数。如下链表的插入问题。


        main函数调用Insert函数,向一个链表head中插入结点node1,这个插入节点的操作分两步,第一步:node->_next = head ,第二步:head = node 。在做完第一步的时候,因为硬件中断使进程切换到内核态,内核处理完异常准备回用户态之前,检查到有信号待处理,于是切换回用户态调用Sighandler函数,而在 Sighandler函数中也调用Insert函数相同一个链表head中插入node2,插入操作完成后从 Sighandler函数返回内核态,再次返回用户态就从main函数调用的Insert函数中继续往下执行。先前做了第一步后被打断,现在继续完成第二步。结果就是main函数和 Sighandler函数先后向链表中插入两个结点,而最后真正插入进来的只有一个结点。
        Insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入函数,这就是重入。
        Insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数就是不可重入函数。

        相对应的,当一个执行流因为异常或者被内核切换而中断正在执行的函数而转为另外一个执行流时,当后者的执行流对同一个函数的操作并不影响前一个执行流恢复后执行函数产生的结果,我们就称这个函数为可重入函数。只访问自己的局部变量或参数。其实总结就是:一个可重入函数可以被多个执行流重复进入,内部使用的数据都应该来自于自身的栈空间,包括返回值也不应该是全局或者静态的,可以允许有该函数的多个副本在运行,而正是因为其中的操作数据都来自于自身的栈空间,而每次调用函数会开辟不同的栈空间,因此二者互不影响。

一个可重入函数需要满足的是:

1、不使用全局变量或静态变量;
2、不返回静态或全局数据,所有数据都有函数的调用者提供;
3、不使用用malloc或者new开辟出的空间;
4、使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据; 
5、不调用不可重入函数;

不可重入函数符合以下条件之一即是:

1、调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的。
2、调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
3、可重入体内使用了静态的数据结构。


线程安全和线程非安全
        线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。换句话说, 一个函数被称为线程安全的,当且仅当被多个并发进程反复调用时,它会一直产生正确的结果。反之,如果一个函数不是线程安全的,我们就说它是线程不安全的。

有以下四类函数是线程不安全的:
1、不保护共享变量的函数;
2、函数状态随着调用改变的函数;
3、返回指向静态变量指针的函数;
4、调用线程不安全函数的函数;

        如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。 或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。

        线程安全问题都是由全局变量及静态变量引起的。 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。 一般来说,一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生正确的结果。

        比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素1存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B向此 ArrayList 添加元素2,因为此时 Size 仍然等于 0 (注意,我们假设的是添加一个元素是要两个步骤,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值,结果Size等于2。
那好,我们来看看 ArrayList 的情况,期望的元素应该有2个,而实际只有一个元素,造成丢失元素,而且Size 等于 2。这就是“线程不安全”了。



可重入与线程安全联系和区别:

联系:
    1. 函数可以是可重入的,也可以是线程安全的,或者两者皆是,或者两者皆非。不可重入函数不能由多个线程使用。
    2.可重入函数是线程安全函数的一种,其特点在于它们被多个线程调用时,不会引用任何共享数据。
    3.可重入函数和线程安全解决的都是对全局或静态数据访问的不一致性。

区别:
   1.线程安全能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作互不影响使结果是相同的。
   2. 线程安全是在多线程情况下引发的,而可重入函数可以在只有一个线程的情况下发生。
   3.线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
   4.如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。
   5.如果一个函数当中的数据全是自身栈空间的,则这个函数既是线程安全也是可重入的。
   6.如果将对临界资源的访问加锁,则这个函数是线程安全的;但如果重入函数的话加锁还未释放,则会产生死锁,因此不能重入。









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值