可重入的解释

本文介绍了可重入和线程安全的概念及其区别。详细解释了如何使函数成为可重入的,并提供了代码示例。同时探讨了两者之间的联系与不同。
声明:本文转载自维基百科http://zh.wikipedia.org/zh/%E5%8F%AF%E9%87%8D%E5%85%A5


可重入的概念

         若一个程序或子程序可以「安全的被并行执行(Parallel computing)」,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,可以再次进入并执行它(并行执行时,个别的执行结果,都符合设计时的预期)。

       可重入概念是在单线程操作系统的时代提出的。

       一个子程序的重入,可能由于自身原因,如执行了jmp或者call,类似于子程序的递归调用;或者由于硬件中断,UNIX系统的signal的处理,即子程序被中断处理程序或者signal处理程序调用。

      重入的子程序,按照后进先出线性序依次执行。

      若一个函数是可重入的,则该函数:
            不能含有静态(全局)非常量数据。
            不能返回静态(全局)非常量数据的地址。
            只能处理由调用者提供的数据。
            不能依赖于单实例模式资源的锁。
            不能调用(call)不可重入的函数(有呼叫(call)到的函数需满足前述条件)。
      多「用户/对象/进程优先级」以及多进程,一般会使得对可重入代码的控制变得复杂。同时,IO代码通常不是可重入的,因为他们依赖于像磁盘这样共享的、单独的(类似编程中的静态(Static)、全域(Global))资源。

可重入的例子

在以下的C语言代码中,函数f和函数g都不是可重入的。
int g_var = 1;
int f(){
  g_var = g_var + 2;
  return g_var;
}
int g(){
  return f() + 2;
}
以上代码中,f使用了全局变量 g_var,所以,如果两个线程同时执行它并访问g_var,则返回的结果取决于执行的时间。因此,f不可重入。而g调用了f,所以它也不可重入。
稍作修改后,两个函数都是可重入的:
int f(int i){
  return i + 2;
}
int g(int i){
  return f(i) + 2;
}

可重入与线程安全的关系

      可重入与线程安全两个概念都关系到函数处理资源的方式。但是,他们有一定的区别。可重入概念会影响函数的外部接口,而线程安全只关心函数的实现。
            大多数情况下,要将不可重入函数改为可重入的,需要修改函数接口,使得所有的数据都通过函数的调用者提供。
            要将非线程安全的函数改为线程安全的,则只需要修改函数的实现部分。一般通过加入同步机制以保护共享的资源,使之不会被几个线程同时访问。
      线程安全与可重入性是两个不同性质的概念。可重入是在单线程操作系统背景下,重入的函数或者子程序,按照后进先出的线性序依次执行完毕。多线程执行的函数或子程序,各个线程的执行时机是由操作系统调度,不可预期的,但是该函数的每个执行线程都会不时的获得CPU的时间片,不断向前推进执行进度。可重入函数未必是线程安全的;线程安全函数未必是可重入的。例如,一个函数打开某个文件并读入数据。这个函数是可重入的,因为它的多个实例同时执行不会造成冲突;但它不是线程安全的,因为在它读入文件时可能有别的线程正在修改该文件,为了线程安全必须对文件加“同步锁”。另一个例子,函数在它的函数体内部访问共享资源使用了加锁、解锁操作,所以它是线程安全的,但是却不可重入。因为若该函数一个实例运行到已经执行加锁但未执行解锁时被停下来,系统又启动该函数的另外一个实例,则新的实例在加锁处将转入等待。如果该函数是一个中断处理服务,在中断处理时又发生新的中断将导致资源死锁。
下述例子,是线程安全的,但不是可重入的。
int function()
{
 mutex_lock();
 ...
 function body
 ...
 mutex_unlock();
}
      多线程执行时,获得了互斥锁的线程总能获得CPU时间片,向前推进执行进度,最终解开互斥锁,使得别的线程也能获得互斥锁进入临界区。但是,如果在单线程背景下第一次执行该函数时已经获得互斥锁进入临界区,这时该函数被重入执行,这将在重新申请互斥锁时被饿死(starvation),因为获得了互斥锁的该函数的第一次执行将永远没有机会再获得CPU时间片。
<think>好的,用户让我介绍一下可重入函数。首先,我需要回忆可重入函数的基本概念,确保自己理解正确。可重入函数是指在多线程环境或递归调用中,可以被同时安全调用的函数,不会因为共享数据而导致问题。这点和线程安全有关,但两者有区别,可能需要解释清楚。 接下来,我得整理可重入函数的特点。比如不使用静态或全局数据,不调用非可重入函数等。然后,需要说明为什么需要可重入函数,比如在信号处理函数、多线程应用中必须使用,否则可能导致数据不一致或崩溃。 然后,应该举一个例子,对比不可重入可重入的函数。比如,一个使用静态变量的函数是不可重入的,而改用参数传递或局部变量的版本是可重入的。代码示例需要用C语言,确保正确性。 可能用户容易混淆可重入和线程安全的概念,需要指出它们的区别。比如,线程安全函数可能通过同步机制保证安全,而可重入函数不需要同步即可在多个执行流中使用。 然后,按照用户的要求,最后要提出相关问题。问题需要与可重入函数相关,比如定义、与线程安全的区别、如何编写、应用场景等。 检查是否有遗漏点,比如可重入函数的使用场景,如信号处理函数必须可重入,因为可能中断其他线程的执行。同时,需要强调编写可重入函数的注意事项,比如避免静态变量,使用局部数据,不调用非可重入函数等。 最后确保回答结构符合用户的要求:先回答问题,给出解释,然后列出相关问题。代码示例要正确,用C语言,并且问题部分要四个相关问题,使用规定的格式。</think><< 可重入函数(Reentrant Function)是指可以被多个执行流(如多线程、中断、信号处理函数等)同时调用而不会引发数据不一致或逻辑错误的函数。它通过避免使用共享资源(如全局变量、静态变量)或通过合理同步机制来保证线程安全。 ### 可重入函数的特点: 1. **不使用全局/静态数据**:所有数据通过参数或局部变量传递。 2. **不调用非可重入函数**:如 `malloc`、`printf` 等可能依赖全局状态的函数。 3. **无副作用**:函数执行不依赖外部隐藏状态(如硬件寄存器)。 ### 不可重入 vs 可重入示例: ```c // 不可重入函数(使用静态变量) int non_reentrant_counter() { static int count = 0; // 静态变量导致线程不安全 return ++count; } // 可重入函数(通过参数传递状态) int reentrant_counter(int *count) { return ++(*count); // 操作局部或外部传入的数据 } ``` ### 需要可重入的场景: 1. **信号处理函数**:信号可能中断正在执行的代码,必须保证可重入。 2. **多线程环境**:多个线程同时调用同一函数时需避免竞态条件。 3. **递归调用**:函数可能间接调用自身,需隔离每次调用的状态。 ### 如何编写可重入函数: - 用局部变量代替全局/静态变量。 - 若必须使用共享数据,需通过互斥锁(如 `pthread_mutex`)保护。 - 避免调用非可重入函数(如标准库中的某些函数)。 ### 可重入与线程安全的区别: - **可重入性**:无需同步即可在多个执行流中安全调用。 - **线程安全**:可能依赖同步机制(如锁)保证多线程安全。 所有可重入函数都是线程安全的,但反之不一定成立。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值