可重入和线程安全

线程安全这个词对我来说已经不是很陌生的了,但是遇到一个叫做可重入函数的词,它给我的感觉和线程安全是这么的相近,但既然拿出来了,肯定是有区别的,下面就说说他们之间的区别和联系。

要先解释这两个词语才行。

线程安全:似乎是在牛客网刷题的时候看到一个正确的选项说的是,线程安全问题都是由全局变量及静态变量引起的

可重入函数:按我现在的理解就是,因为不同的执行流执行同一个函数,导致函数的执行顺序和预期的函数执行顺序不同导致执行逻辑不正确,得不到正确的结果

这么一看,可重入函数和线程安全似乎没有什么大的关系,下面给出两个比较典型的例子,来看一下。

线程安全:先说一下环境,如下面的代码表示的一样,a和b都是静态变量(也就是说是全局可见的),两段代码都在同一个项目中

1 static int a; 2 static int b; 3 a=10;
//!!!!!!!!
4 b=a;
a=5;
//!!!!!! a
=b;

看着两段代码似乎没什么关系,各自执行各自的,但是当有两个线程(都属于同一个进程)同时跑这个程序的时候,问题就出现了,也就是线程A和线程B的执行顺序:

线程A:a=10;

线程B:a=5;

线程A:b=a;

线程B:a=b;

本来b是要等于10的,但由于线程B先执行了a=5导致b最终结果编程5出现了错误。这就是一个简单的线程安全的例子。

 

可重入函数:常见的情况是,程序执行到某个函数foo()时,收到信号,于是暂停目前正在执行的函数,转到信号处理 函数,而这个信号处理函数的执行过程中,又恰恰也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入,那么这么做的结果会变成什么样子呢?

当执行完第一次insert的时候,假设此时发生了中断,导致系统陷入内核,当系统准备从内核态切换到用户态的时候检查到此时有信号待处理,于是调用信号处理函数(就是注册好的signalhandler啦),但是恰好注册的函数中有一次对insert进行了调用,这就相当于有两个人同时进入了一个小黑屋里一样,有多个执行流在对该函数进行操作,最终导致内存泄漏,丢失了对node2的控制。

 

这样看来,线程安全和可重入的问题都是因为执行流的问题引起的了。

很显然,如果一个函数是可重入函数,那么它一定是线程安全的,但是如果一个函数是线程安全的,它不一定是可重入函数,就跟上图中举得例子一样的,很明显insert函数是线程安全的,但是它并不是可重入函数

 

那么究竟什么是可重入函数的定义呢?

在多线程或有异常控制流的情况下,当某个函数运行到中途时,控制流(也就是当前指令序列)就有可能被打断而去执行另一个函数.而"另一个函数"很有可能是它本身.,如果在这种情况下不会出现问题,比如说数据或状态不会被破坏,行为确定。那么这个函数就被称做"可重入"的.

可重入函数需要满足什么条件呢?

  • 不能使用malloc系列函数,因为malloc函数内部是通过全局链表实现的
  • 不可以调用标准I/O库函数,这些库函数很多都不是可重入的
  • 肯定不能有全局或者静态变量,否则连线程安全都不满足了