深入理解线程安全和可重入函数

线程安全

一个线程当且仅当被多个并发进程反复调用时,它会一直产生正确的结果。反之,就是不安全的。

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

线程安全的问题基本上是由全局变量及静态变量引起的,因为同一进程中的多线程是在同一个地址空间中运行,而全局变量和静态变量就属于多线程共享的资源。若每个线程中对全局变量、静态变量只有读操作,一般来讲,这个全局变量是线程安全的。若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能会影响到线程安全。

多个线程之间私有的包括各个线程的上下文信息,私有栈,也就是说或局部变量不会影响线程安全

代码说明线程安全问题
深入理解线程安全和可重入函数
按照我们预想,第一个线程将count加到1000,第二个线程将count从1000加到2000,但实际上,三次运行的结果都不同,所以fun函数是不是线程安全的

那么原因是什么呢?
我们首先要清楚i++不是原子操作,包括三步:从内存中将数据放入寄存器,寄存器进行+1操作,将新的数据写回到内存中。
我们定义的tmp,例如第一个线程将count加到150时,刚用tmp保存了count的值,然后被切出去,第二个线程执行count加到360,然后切回到第一个线程,第一个线程执行count=tmp+1,此时tmp为150,count值为151,所以count的值被改。这样两个线程间就产生了干扰,造成线程不安全
深入理解线程安全和可重入函数
我们将代码做以修改:
深入理解线程安全和可重入函数
运行后,就不会出现运行结果不一致的问题了,修改后的函数是线程安全的。

可重入函数

函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,称为重入。

可重入函数主要用于多任务环境中,一个可重入函数可以理解为“可以被中断的函数”,也就是说,可以在这个函数执行的任何时刻中断它,转入操作系统调度另外一段代码,而返回控制时不会出现什么错误。而不可重入函数由于使用了一些系统资源,比如全局变量区、中断向量表等,所以如果被中断后,可能会出现问题,所以不可重入函数是不能运行在多任务环境下。

举个例子:(为不可重入函数)
深入理解线程安全和可重入函数
深入理解线程安全和可重入函数
这段程序我们希望输出的结果是3,但我们输入Ctrl+C,产生错误,由于变量是全局的,收到2号信号,继续进行+1操作,结果运行为6。所以,该程序中的fun函数是不可重入的

可重入函数的分类
1.显式可重入函数
若所有函数的参数都是传值传递的(不含指针),并且所有数据引用都是本地的自动栈变量(即没有引用静态或全局变量),那么函数就是显示可重入的。
2.隐式可重入函数
可重入函数中的一些参数是引用传递(含指针),即调用线程小心地传递指向非共享数据的指针时,是可重入的。

重入函数需要满足以下条件:
1.不使用全局变量或静态变量;
2.不使用malloc或new开辟出的空间;
3.不调用不可重入函数;
4.不返回静态或全局数据,所有数据均由函数的调用者提供;
5.使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。

不可重入函数只需满足以下条件中的一个:
1.调用了malloc/free函数,原因是malloc函数是用全局链表来管理堆的;
2.调用了标准I/O库函数,原因是标准I/O库的很多实现都是以不可重入的方式使用全局数据结构的;
3.可重入体内使用了静态的数据结构

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