- 定义
线程安全:被多个并发的线程反复调用时,他会产生正确的结果。
可重入:当被多个线程调用的时候,不会引用任何共享数据。
Remark:任何线程不安全问题的根源都是“共享数据”。所以,不使用任何共享数据的函数(即:可重入函数)肯定是线程安全的。但这并不等于说,线程安全函数就是可重入函数。为什么呢?因为:即使有线程有共享数据,线程被并发调用的时候产生的结果可以是正确的。那么这种正确性是如何保证的呢?对,就是通过同步操作。
- 线程不安全的根源
上面已经提到,线程不安全的根源在于:共享数据。
共享数据可以是:
函数把返回结果放到一个公共的位置- 由调用者传入的线程间共享的指针变量或者引用变量
函数内部本来就会使用的共享静态变量
- 线程不安全怎么能够改写成线程安全
无非有两种策略:
- 仍旧使用共享数据,但是在使用共享数据的时候做同步操作。对于函数过程中使用的共享数据,可以进行简单的PV操作,对于返回结果可以在PV操作中把共享数据拷贝到非共享的位置,以便及时释放共享变量。
- 杜绝使用共享变量。也就是说把函数改成了可重入的函数。但是,彻底杜绝共享变量有的时候不容易做到。对于上面的提到的第一种和第三种情况很容易做到,但是对于第二种情况,我们没有办法。所以,对于一个不接受引用和指针的函数,我们可以把它做到绝对的可重入,但是,对于一个接受指针或者引用的函数来说,对不起,我们不能确保他肯定是可重入的。
- 关于性能
通常来说,多线程是为了在同一时间内能够处理更多的同样类型的事情,但是线程不安全却阻碍了我们达到我们的目的。所以,我们有的时候不得不想方设法的把线程不安全的函数改写成线程安全的。
改写的结果无非两种,一种是原函数的“同步版本”,一种是原函数的“可重入版本”。可重入版本相比前者的从性能上来说有着天然的优势,这种优势就是在于它不涉及PV操作,不存在软件上的瓶颈,可以最大化的利用硬件资源。然而“同步版本”则有可能不能充分利用硬件的资源,因为程序在等待资源的释放。
- 关于重写的策略
拿到一个线程不安全函数是一件郁闷的事情。但是有的时候你必须要使用它,那怎么办呢?这个时候就要从上面提到的三个可能的共享数据来入手了:
- 如果调用函数的返回结果是共享的,这个时候就要使用上面蓝色字体提到的lock-and-copy方法,在PV操作中,把结果转移到非共享的区域。
- 尝试在调用函数的时候不使用引用或者指针,如果函数在多线程工作的时候结果正确了,则为题解决了,否则,问题可能出现在函数内部,转3
- 找到这个函数的共享变量,对其做PV操作。什么,你没有源代码?“男人,哭吧,哭吧,不是罪……”