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