大多数时候,再入的定义引用自维基百科:
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). To be reentrant, a
computer program or routine:
Must hold no static (or global)
non-constant data.
Must not return the address to
static (or global) non-constant
data.
Must work only on the data provided
to it by the caller.
Must not rely on locks to singleton
resources.
Must not modify its own code (unless
executing in its own unique thread
storage)
Must not call non-reentrant computer
programs or routines.
如何定义安全?
如果一个程序可以安全地并发执行,那么它是否总是意味着它是可重入的?
在检查代码的重入功能时,我应该记住的这六点之间的共同点究竟是什么?
同时,
是否所有递归函数都是可重入的?
是否所有线程安全的函数都是可重入的?
是否所有递归和线程安全的函数都是可重入的?
在写这个问题的时候,我想到了一件事:
像重入和线程安全这样的术语是绝对的吗?也就是说,它们有固定的具体定义吗?因为,如果不是,这个问题就没有多大意义。
所列出的要点中的“共同主线”(双关语!?)是函数不能做任何会影响对同一函数的任何递归或并发调用的行为的事情。
比如静态数据就是个问题因为它属于所有线程;如果一个调用修改了一个静态变量,那么所有线程都会使用修改后的数据,从而影响它们的行为。自修改代码(虽然很少遇到,在某些情况下被阻止)将是一个问题,因为尽管有多个线程,但只有一个代码副本;代码也是基本的静态数据。
从本质上讲,要实现可重入,每个线程必须能够使用函数,就像它是唯一的用户一样,如果一个线程可以以不确定的方式影响另一个线程的行为,则情况就不是这样了。这主要涉及到每个线程都有函数所处理的独立数据或常量数据。
综上所述,第(1)点不一定是正确的;例如,您可以合理地设计使用静态变量来保留递归计数,以防止过度递归或分析算法。
A thread-safe function need not be reentrant; it may achieve thread safety by specifically preventing reentrancy with a lock, and point (6) says that such a function is not reentrant. Regarding point (6), a function that calls a thread-safe function that locks is not safe for use in recursion (it will dead-lock), and is therefore not said to be reentrant, though it may nonetheless safe for concurrency, and would still be re-entrant in the sense that multiple threads can have their program-counters in such a function simultaneously (just not with the locked region). May be this helps to distinguish thread-safety from reentarncy (or maybe adds to your confusion!).
现在我要详细说明一下我刚才的评论。@paercebal回答不正确。在示例代码中,难道没有人注意到应该是参数的互斥量实际上没有传入吗?
我对这个结论提出异议,我断言:一个函数要在并发性存在的情况下是安全的,它必须是可重入的。因此,并发安全的(通常是编写线程安全的)意味着可重入。
无论是线程安全的还是可重入的,都与参数无关:我们讨论的是函数的并发执行,如果使用了不适当的参数,仍然可能是不安全的。
例如,memcpy()是线程安全的和可重入的(通常)。显然,如果从两个不同的线程调用指向相同目标的指针,它将无法正常工作。这就是SGI定义的要点,将确保对相同数据结构的访问由客户端同步的责任放在了客户端身上。
重要的是要理解,在一般情况下,让线程安全操作包含参数是没有意义的。如果你做过数据库编程,你就会明白。“原子的”概念以及可能由互斥锁或其他技术保护的概念必然是用户概念:在数据库上处理事务可能需要多次不间断的修改。除了客户端程序员,谁能说哪些需要保持同步呢?
关键是,“损坏”并不一定要用非序列化的写入来搞乱您计算机上的内存:即使所有单独的操作都是序列化的,损坏仍然可能发生。因此,当您询问一个函数是否是线程安全的或可重入的时,这个问题意味着所有适当分离的参数:使用耦合参数并不构成反例。
有很多编程系统:Ocaml是其中之一,我认为Python也是,它们有很多不可重入的代码,但是使用全局锁来交错线程访问。这些系统不是可重入的,它们不是线程安全的或并发安全的,它们安全运行只是因为它们阻止了全局并发。
malloc就是一个很好的例子。它不是可重入的,也不是线程安全的。这是因为它必须访问全局资源(堆)。使用锁并不能保证它的安全性:它绝对不能重入。如果malloc的接口设计得当,就有可能使其可重入且线程安全:
malloc(heap*, size_t);
现在它是安全的,因为它将序列化对单个堆的共享访问的责任转移到客户机。特别是,如果存在单独的堆对象,则不需要做任何工作。如果使用公共堆,客户端必须序列化访问。在函数内部使用锁是不够的:只考虑一个malloc锁定一个堆*,然后一个信号出现并在同一个指针上调用malloc:死锁:信号不能继续,客户端也不能继续,因为它被中断了。
一般来说,锁并不能使事情变得线程安全。它们实际上破坏了安全性,因为它们不恰当地试图管理客户端拥有的资源。锁定必须由对象制造商完成,这是唯一的代码,知道有多少对象被创建和他们将如何使用。
术语“线程安全的”和“可重入的”的意思完全是它们定义的意思。在这种情况下,“安全”只是指你在下面引用的定义。
这里的“安全”当然不是指更广泛意义上的安全,即在给定的上下文中调用给定的函数不会完全覆盖您的应用程序。总之,一个函数可能在多线程应用程序中可靠地产生期望的效果,但根据定义,它既不符合可重入性,也不符合线程安全。相反,在多线程应用程序中调用可重入函数的方式会产生各种不需要的、意想不到的和/或不可预测的效果。
递归函数可以是任何东西,重入函数的定义比线程安全的更强,所以你的问题的答案都是否定的。
在阅读reentrant的定义时,人们可能会将其总结为一个函数,该函数不会修改任何超出您所要修改的内容的内容。但你不应该只依赖总结。
多线程编程在一般情况下是非常困难的。知道代码的哪一部分可以重入只是这个挑战的一部分。线程安全不是附加的。与其试图将可重入函数拼凑在一起,不如使用整体线程安全的设计模式,并使用此模式来指导程序中每个线程和共享资源的使用。