1

可重入性作为多线程编程里面重要的概念,相信大家或多或少都听说过、研究过甚至使用过。目前很多资料都重点在介绍重入锁、CAS等概念,对“可重入性”本身的讲解不是很多,所以JavaCool给大家整理了一些“可重入性”的相关知识,希望能帮助到大家!

* 可重入的定义
  • 简单定义:"可以正确重复使用",有两个关键:1,可以重复使用;2,并能正确使用。意味着在多次执行的时候能得到正确的值,并不受其他调用的影响。
  • 官方定义:若一个程序或子程序可以“在任意时刻被中断然后操作系统调度执行另外一段代码,这段代码又调用了该子程序不会出错”,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,执行线程可以再次进入并执行它,仍然获得符合设计时预期的结果。与多线程并发执行的线程安全不同,可重入强调对单个线程执行时重新进入同一个子程序仍然是安全的。
这里也有一段比较好的英文阐释:
A computer program or routine is described as reentrant if it can be safely called again before its previous invocation has been completed (i.e it can be safely executed concurrently)

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

* 产生背景

可重入概念是在单线程操作系统的时代提出的。一个子程序的重入,可能由于自身原因,如执行了jmp或者call,类似于子程序的递归调用;或者由于操作系统的中断响应。UNIX系统的signal的处理,即子程序被中断处理程序或者signal处理程序调用。所以,可重入也可称作“异步信号安全”。这里的异步是指信号中断可发生在任意时刻。 重入的子程序,按照后进先出线性序依次执行。

* 编写可重入代码注意的条件

若一个函数是可重入的,则该函数应当满足下述条件:

  1. 不能含有静态(全局)非常量数据。
  2. 不能返回静态(全局)非常量数据的地址。
  3. 只能处理由调用者提供的数据。
  4. 不能依赖于单实例模式资源的锁。
  5. 调用(call)的函数也必需是可重入的。

上述条件就是要求可重入函数使用的所有变量都保存在呼叫堆叠的当前函数栈(frame)上,因此同一执行线程重入执行该函数时加载了新的函数帧,与前一次执行该函数时使用的函数帧不冲突、不互相覆盖,从而保证了可重入执行安全。

多“用户/对象/进程优先级”以及多进程(Multiple processes),一般会使得对可重入代码的控制变得复杂。同时,IO代码通常不是可重入的,因为他们依赖于像磁盘这样共享的、单独的(类似编程中的静态、全域)资源。

* 与线程安全的关系

可重入与线程安全两个概念都关系到函数处理资源的方式。但是,他们有重大区别:可重入概念会影响函数的外部接口,而线程安全只关心函数的实现。大多数情况下,要将不可重入函数改为可重入的,需要修改函数接口,使得所有的数据都通过函数的调用者提供。要将非线程安全的函数改为线程安全的,则只需要修改函数的实现部分。一般通过加入同步机制以保护共享的资源,使之不会被几个线程同时访问。

  • 操作系统背景与CPU调度策略:

可重入是在单线程操作系统背景下,重入的函数或者子程序,按照后进先出的线性序依次执行完毕。

多线程执行的函数或子程序,各个线程的执行时机是由操作系统调度,不可预期的,但是该函数的每个执行线程都会不时的获得CPU的时间片,不断向前推进执行进度。可重入函数未必是线程安全的;线程安全函数未必是可重入的。

  • 例如,一个函数打开某个文件并读入数据。这个函数是可重入的,因为它的多个实例同时执行不会造成冲突;但它不是线程安全的,因为在它读入文件时可能有别的线程正在修改该文件,为了线程安全必须对文件加“同步锁”。
  • 另一个例子,函数在它的函数体内部访问共享资源使用了加锁、解锁操作,所以它是线程安全的,但是却不可重入。因为若该函数一个实例运行到已经执行加锁但未执行解锁时被停下来,系统又启动该函数的另外一个实例,则新的实例在加锁处将转入等待。如果该函数是一个中断处理服务,在中断处理时又发生新的中断将导致资源死锁。fprintf函数就是线程安全但不可重入。
* 可重入锁

可重入锁也叫递归锁,它俩等同于一回事,指的是同一线程外层函数获得锁之后,内层递归函数仍然能获得该锁的代码,同一线程在外层方法获取锁的时候,再进入内层方法会自动获取锁。也就是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。ReentrantLock 和 synchronized 就是典型的可重入锁!

更多内容可以关注我们的公众号:JavaCool
qrcode_for_gh_e988fff84451_344.jpg

* 参考资料

https://zh.wikipedia.org/wiki...
https://stackoverflow.com/que...
http://www.longpelaexpertise....
https://baike.baidu.com/item/...


Gavin
3 声望0 粉丝