“Code Re-entrancy”和“Thread Safety”的概念之间有什么区别?根据下面提到的链接,一段代码可以是它们中的任何一个,它们都是,或者都不是。
Reentrant and Thread safe code
我无法清楚地理解解释。帮助将不胜感激。
答案 0 :(得分:21)
可重入代码在单个点中没有状态。您可以在代码中执行某些操作时调用代码。如果代码使用全局状态,一个调用可以想象覆盖全局状态,打破另一个调用中的计算。
线程安全代码是没有竞争条件或其他并发问题的代码。竞争条件是两个线程执行某些操作的顺序会影响计算。典型的并发问题是可以部分完成对共享数据结构的更改并使其处于不一致状态。为了避免这种情况,您必须使用并发控制机制(如互斥锁的信号量)来确保在操作完成之前没有其他任何东西可以访问数据结构。
例如,如果一段代码在外部由互斥锁保护,但仍具有全局数据结构,其中状态必须在整个调用期间保持一致,那么一段代码可以是不可重入的但是线程安全的。在这种情况下,同一个线程可以启动对过程的回调,同时仍受外部粗粒度互斥锁的保护。如果在非重入过程中发生回调,则调用可能使数据结构处于可能从调用者的角度打破计算的状态。
一段代码可以是重入但非线程安全的,如果它可以对共享(和可共享)数据结构进行非原子更改,可以在更新过程中中断数据结构一个不存在的国家。在这种情况下,访问数据结构的另一个线程可能会受到半更改数据结构的影响,并且会崩溃或执行破坏数据的操作。
答案 1 :(得分:8)
那篇文章说:
“一个函数可以是可重入的,线程安全的,两者都可以。”
它还说:
“非重入函数是线程不安全的”。
我可以看到这可能会导致混乱。它们意味着记录为不需要重入的标准函数也不需要是线程安全的,这对于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();
在单线程应用程序上,这很好,假设操作系统支持禁用所有需要禁用的内容。它可以防止在临界点发生重新入侵。根据信号的禁用方式,从信号处理程序调用可能是安全的,尽管在这个特定的例子中,传递给回调的参数问题对于单独的调用仍然是相同的。但是,它仍然可能出错多线程。
实际上,非线程安全通常意味着不可重入,因为(非正式地)由于线程被调度程序中断而可能出错的任何事情,以及从另一个线程再次调用的函数,也可以如果线程被信号中断,则错误,并且从信号处理程序再次调用该函数。但是,防止信号(禁用它们)的“修复”与“修复”不同,以防止并发(通常是锁)。这充其量只是一条经验法则。
请注意,我在这里隐含了全局变量,但如果函数将参数作为指针指向计数器和锁定,则会应用完全相同的注意事项。只是当使用相同的参数调用时,各种情况都是线程不安全或不可重入的,而不是在被调用时。