在了解可重入与线程安全之前,我们需要明确一点,可重入与线程安全是不同的概念。
我们以一个例子引入可重入与不可重入:假设现在要往链表中插入一结点,而对于信号的处理也是调用insert函数,在此,insert函数被不同的执行流进入,这称为重入。但insert访问一个全局链表,有可能因为重入导致错误的结果,则insert是不可重入函数。
![](https://img-blog.csdn.net/20170224112629097?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzM5NTExODA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
(2)调用了标准I/O库函数,标准I/O库的很多实现都是以不可重入的方式使用全局的数据结构
(3)可重入体内使用了静态的数据结构
一、可重入函数(Reentrant Function)
- 重入:即重复调用,多个执行流可进入同一个函数,有可能会出现第一次调用还没返回就再次进入该函数进行下一次调用
- 可重入:重入期间不会出现任何的安全问题,即该函数是可被重入的
- 不可重入:与可重入相反,当多个执行流进入统一函数时,一个线程有可能影响另一个线程,会产生一定的风险,即该函数是不可被重入的
- 确保可重入的条件:
1.不在函数内部使用静态或全局变量2.不返回静态或全局变量,所有数据都有函数的调用者提供。3.使用本地数据,或通过制作全局数据的本地拷贝来保护全局数据4.不调用不可重入函数
- 不可重入的特点:如果一个函数符合以下条件之一则是不可重入的
(2)调用了标准I/O库函数,标准I/O库的很多实现都是以不可重入的方式使用全局的数据结构
(3)可重入体内使用了静态的数据结构
- 不可重入的后果:主要体现在像信号处理这样需要重入的函数中,如果信号处理函数中使用了不可重入的函数,则可能导致程序的错误或崩溃(可以按照上例进行分析)
二、线程安全(Thread-Safe)
- 线程安全:一般来说,一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生一个正确的结果。
- 线程不安全的后果:共享变量的值由于不同的线程访问,可能发生不可预料的变化,进而导致程序的错误甚至崩溃
- 确保线程安全:要确保线程安全,主要考虑的是线程的共享变量。属于同一进程的不同线程会共享进程内存空间中的全局区和堆,而私有的线程空间主要包括栈空间和寄存器。因此,对于同一进程的不同线程来说,每个线程的局部变量都是私有的,而全局变量、局部静态变量、分配于堆的变量都是共享的。在对这些变量进行访问时,必须通过加锁的方式来保证线程安全。
三、可重入函数与线程安全的区别与联系
- 线程安全是在多个线程情况下引发的,而可重入函数可以在只有一个线程的情况下发生
- 线程安全不一定是可重入的,而可重入函数一定是线程安全的
- 如果一个函数中有全局变量,则该函数既不是线程安全的也是不可重入的
- 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数的锁还未释放则会产生死锁,因此是不可重入的
- 线程安全的函数能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作互不影响使结果是相同的
总之,可重入函数一定是线程安全的;线程安全的函数可能是重入的,也可能是不可重入的;线程不安全的函数一定是不可重入的。