Multithreading 代码重入与线程安全

Multithreading 代码重入与线程安全,multithreading,thread-safety,reentrancy,Multithreading,Thread Safety,Reentrancy,“代码重入性”和“线程安全性”的概念有什么区别?根据下面提到的链接,一段代码可以是它们中的任何一个,两个都可以,或者两者都不能 我无法清楚地理解解释。我们将不胜感激 重入代码在单个点中没有状态。您可以在代码中执行某些内容时调用代码。如果代码使用全局状态,一个调用可能会覆盖全局状态,从而中断另一个调用中的计算 线程安全代码是没有竞争条件或其他并发问题的代码。竞态条件是两个线程执行某项操作的顺序影响计算的情况。一个典型的并发性问题是,对共享数据结构的更改可能部分完成,并处于不一致状态。为了避免这种

“代码重入性”和“线程安全性”的概念有什么区别?根据下面提到的链接,一段代码可以是它们中的任何一个,两个都可以,或者两者都不能


我无法清楚地理解解释。我们将不胜感激

重入代码在单个点中没有状态。您可以在代码中执行某些内容时调用代码。如果代码使用全局状态,一个调用可能会覆盖全局状态,从而中断另一个调用中的计算

线程安全代码是没有竞争条件或其他并发问题的代码。竞态条件是两个线程执行某项操作的顺序影响计算的情况。一个典型的并发性问题是,对共享数据结构的更改可能部分完成,并处于不一致状态。为了避免这种情况,您必须使用并发控制机制,如互斥体的信号量,以确保在操作完成之前,其他任何东西都无法访问数据结构

例如,如果一段代码在外部由互斥体保护,但仍然具有全局数据结构,其中状态必须在整个调用期间保持一致,则该代码可以是非重入的,但线程安全。在这种情况下,同一线程可以在仍受外部粗粒度互斥体保护的情况下发起对过程的回调。如果回调发生在非重入过程中,则调用可能会使数据结构处于一种可能从调用方的角度中断计算的状态

一段代码可以是可重入但非线程安全的,如果它可以对共享的(可共享的)数据结构进行非原子的更改,该数据结构可以在更新的中间被中断,从而使数据结构处于不兼容状态。在这种情况下,另一个访问数据结构的线程可能会受到半更改的数据结构的影响,或者崩溃,或者执行破坏数据的操作。

那篇文章说:

函数可以是可重入的、线程安全的、两者都可以,也可以两者都不能

它还说:

“不可重入函数是线程不安全的”

我知道这会引起混乱。这意味着被记录为不需要重新进入的标准函数也不需要是线程安全的,POSIX库iirc也是如此(POSIX声明ANSI/ISO库也是如此,ISO没有线程概念,因此没有线程安全概念)。换句话说,“如果一个函数说它是不可重入的,那么它说它的线程也不安全”。这不是逻辑上的必然,只是一种惯例

这里有一些线程安全的伪代码(由于锁反转,回调有很多机会创建死锁,但假设文档包含足够的信息,用户可以避免死锁),但不是可重入的。它应该递增全局计数器,并执行回调:

take_global_lock();
int i = get_global_counter();
do_callback(i);
set_global_counter(i+1);
release_global_lock();
如果回调再次调用此例程,导致另一个回调,那么两个级别的回调将获得相同的参数(这可能是正常的,取决于API),但计数器将只增加一次(这几乎肯定不是您想要的API,因此必须禁止)

当然,这是假设锁是递归的。如果锁是非递归的,那么当然代码无论如何都是不可重入的,因为第二次使用锁不会起作用

下面是一些伪代码,它是“弱可重入”的,但不是线程安全的:

int i = get_global_counter();
do_callback(i);
set_global_counter(get_global_counter()+1);
int i = get_global_counter();
do_callback(i);
disable_signals(); // and any other kind of interrupts on your system
set_global_counter(get_global_counter()+1);
restore_signal_state();
现在可以从回调调用函数,但是从不同线程并发调用函数是不安全的。从信号处理程序调用它也不安全,因为如果信号恰好在正确的时间出现,那么从信号处理程序重新进入也会破坏计数。因此,根据正确的定义,代码是不可重入的

这里有一些代码可以说是完全可重入的(除了我认为标准区分了可重入和“信号不可中断”,我不确定这两种代码在哪里),但仍然不是线程安全的:

int i = get_global_counter();
do_callback(i);
set_global_counter(get_global_counter()+1);
int i = get_global_counter();
do_callback(i);
disable_signals(); // and any other kind of interrupts on your system
set_global_counter(get_global_counter()+1);
restore_signal_state();
在单线程应用程序上,假设操作系统支持禁用所有需要禁用的内容,这是可以的。它可以防止在临界点出现重新进入。根据禁用信号的方式,从信号处理程序调用可能是安全的,尽管在这个特定示例中,对于单独的调用,传递给回调的参数仍然存在相同的问题。不过,多线程仍然可能出错

在实践中,非线程安全通常意味着不可重入,因为(非正式地)由于调度程序中断线程和从另一个线程再次调用函数而可能出错的任何事情,如果线程被信号中断,并且从信号处理程序再次调用函数,也可能出错。但是防止信号(禁用信号)的“修复”与防止并发(通常是锁)的“修复”不同。这充其量只是一条经验法则


请注意,我在这里暗示了globals,但如果函数将指向计数器和锁的指针作为参数,则同样的考虑也适用。只是当使用相同的参数调用时,各种情况都是线程不安全或不可重入的,而不是在调用时。

我觉得您的第二个示例听起来不是可重入的。如果更改可以被中断,留下不一致的状态,并且在该点出现信号,并且该信号的处理程序调用该函数,则通常它会停止。这是一个重入问题,而不是线程安全问题。你说得对——正如你在下面所说的那样,你还必须禁用信号才能使后一个示例有效地重入。@concerndoftunbridgewells,如果func在内部使用heap,这是一个很好的改变