线程安全:
一个函数被称为线程安全的,当且仅当被多个进程反复调用时,它会一直产生正确的结果。
有四类函数称为线程不安全函数:
- 不保护共享变量的函数
- 函数状态随着调用改变的函数
- 返回指向静态变量指针的函数
- 调用线程不安全函数的函数
要确保函数线程安全,主要需要考虑的是线程之间的共享变量。属于同一进程的不同线程会共享进程内存空间中的全局区和堆,而私有的线程空间则主要包括栈和寄存器。因此,对于同一进程的不同线程来说,每个线程的局部变量都是私有的,而全局变量、局部静态变量、分配于堆的变量都是共享的。在对这些共享变量进行访问时,如果要保证线程安全,则必须通过加锁的方式。
可重入函数:
函数被不同控制流程调用,有可能在第一次调用还没返回时就在次进入该函数,这称为重入。
当程序运行某一个函数时,这个函数
对一个全局变量进行处理,可能会因为中断或者异常而陷入内核,这时如果有信号去处理,而处理信号的动作会在次调用这个函数,那么当信号处理完成之后恢复中断函数的上下文信息在次执行时,
对于全局变量来说已经发生了改变,所以函数对其进行操作的结果也会发生改变,得到一个我们预期不到的结果。系统在运行时有很多不确定性,有可能会产生信号也有可能不会产信号,所以运行结果可能正确也有可能出错,这样的函数称为
不可重入函数。
可重入函数只访问自己的局部变量或者参数。
一个可重入函数可以被多个执行流重复进入,内部使用的数据都应该来自于自身的栈空间,包括返回值也不应该是全局或者静态的,可以允许有该函数的多个副本在运行,而正是因为其中的操作数据都来自于自身的栈空间,而每次调用函数会开辟不同的栈空间,因此二者互不影响。
不可重入函数的实例:
#include<stdio.h>
#include<stdlib.h>
int i = 0;
void add()
{
i++;
}
void handler(int sig)
{
int j = 0;
while(j < 5)
{
j++;
add();
}
}
int main()
{
signal(2, handler);
int count = 0;
while(count < 6)
{
sleep(1);
count += 1;
add();
printf("i = %d\n", i);
}
return 0;
}
add()函数就是一个不可重入函数,它在没有收到信号和收到信号时的结果完全不同。
可重入函数需要满足的条件:
- 不使用全局变量或者静态变量
- 不使用malloc或者new开辟新空间
- 不能调用不可重入函数
- 所有数据都由函数的调用者提供
- 使用本地数据,或者通过制作全局数据的本地拷贝保护全局数据
不可重入函数符合以下条件之一:
- 调用malloc或者free函数,因为malloc函数是用全局链表管理堆的
- 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
- 使用了静态数据结构
可重入函数和线程安全的联系和区别:
- 线程安全是在多线程情况下引发的,而可重入函数可以在只有一个线程的情况下发生
- 线程安全不一定是可重入的,而可重入函数一定是线程安全的
- 一个函数有全局变量,这个函数既不是线程安全的也不是可重入的
- 如果一个函数的数据全是自己栈空间的数据,那么这个函数是线程安全的,也是可重入的
- 如果对临界资源进行加锁,则这个函数是线程安全的,如果是可重入函数加锁的话,锁不能被释放,会产生死锁,所以仍是不可重入函数
- 线程安全函数能够让不同的线程访问同一块地址空间,而可重入函数要求不同的执行流重复的操作不影响结果,使每次操作结果都相同