Most of the times,重新引入的定义引自Wikipedia:
计算机程序或例程 如果它可以被描述为可重入的 安全地 再次调用它 之前的调用已经完成 (即可以安全地执行 兼)。要是可重入的,a 计算机程序或例程:
- 必须保持不静态(或全局) 非常数数据。
- 不得将地址退回 静态(或全局)非常数 数据。
- 必须仅对提供的数据有效 来电者。
- 一定不要依赖锁定单身 资源。
- 不得修改自己的代码(除非 在自己独特的线程中执行 存储)
- 不得拨打非重入计算机 程序或惯例。
醇>
如何定义 安全 ?
如果程序可以 同时安全执行 ,它是否始终意味着它是可重入的?
在检查我的代码的可重入功能时,我应该记住的六点之间的共同点是什么?
此外,
在写这个问题时,我想到了一件事: 重入和线程安全之类的术语是否完全绝对,即它们是否具有固定的具体定义?因为,如果他们不是,这个问题就没有意义了。
答案 0 :(得分:176)
答案 1 :(得分:21)
“安全”的定义与常识所指的完全相同 - 它意味着“在不干扰其他事情的情况下正确地做事”。你引用的六点非常清楚地表达了实现这一点的要求。
你的3个问题的答案是3ד不”。
所有递归函数都是可重入的吗?
否<!/强>
如果,两个同时调用递归函数很容易搞砸彼此 例如,它们访问相同的全局/静态数据。
所有线程安全的函数都是可重入的吗?
否<!/强>
如果函数同时调用则不会出现故障,则该函数是线程安全的。但这可以实现,例如通过使用互斥锁来阻止第二次调用的执行,直到第一次完成,因此一次只能执行一次调用。重入意味着并行执行而不会干扰其他调用。
所有递归和线程安全的函数都是可重入的吗?
否<!/强>
见上文。
答案 2 :(得分:10)
共同主题:
如果例程在被中断时被调用,那么行为是否被很好地定义了?
如果你有这样的功能:
int add( int a , int b ) {
return a + b;
}
然后它不依赖于任何外部状态。行为定义明确。
如果你有这样的功能:
int add_to_global( int a ) {
return gValue += a;
}
结果在多个线程上没有很好地定义。如果时机错误,信息可能会丢失。
可重入函数的最简单形式是仅对传递的参数和常量值进行操作。其他任何事情需要特殊处理,或者通常不是可重入的。当然,参数不能引用可变全局变量。
答案 3 :(得分:7)
现在我必须详细说明我之前的评论。 @paercebal答案不正确。在示例代码中没有人注意到作为参数的互斥锁实际上没有被传入?
我对结论提出异议,我断言:为了使函数在并发存在的情况下是安全的,它必须是可重入的。因此,并发安全(通常是编写的线程安全)意味着可重入。
线程安全和重入都没有任何关于参数的说法:我们讨论的是函数的并发执行,如果使用了不适当的参数,这仍然是不安全的。
例如,memcpy()是线程安全的并且可以重入(通常)。显然,如果使用指向两个不同线程的相同目标的指针调用它将无法按预期工作。这就是SGI定义的重点,将责任放在客户端上以确保客户端同步对相同数据结构的访问。
重要的是要理解,一般来说,无意义让线程安全操作包含参数。如果您已完成任何数据库编程,您将理解。什么是“原子”并且可能受互斥或其他技术保护的概念必然是用户概念:在数据库上处理事务可能需要多次不间断的修改。谁可以说哪些需要与客户端程序员保持同步?
关键是“腐败”并不一定会破坏计算机上的内存并使用反序列化的写入:即使所有单个操作都被序列化,仍然可能发生损坏。因此,当你询问一个函数是线程安全的还是可重入的时,这个问题意味着所有适当分离的参数:使用耦合参数不构成一个反例。
有很多编程系统:Ocaml就是其中之一,我认为Python也有很多不可重入的代码,但它使用全局锁来交错线程访问。这些系统不是可重入的,并且它们不是线程安全的或并发安全的,它们安全地运行只是因为它们会阻止全局并发。
一个很好的例子是malloc。它不是可重入的,也不是线程安全的。这是因为它必须访问全局资源(堆)。使用锁不会使其安全:它绝对不是可重入的。如果malloc的接口设计得当,那么可以使它重新进入并保证线程安全:
malloc(heap*, size_t);
现在它可以是安全的,因为它将串行化对单个堆的共享访问的责任转移到客户端。特别是如果有单独的堆对象,则不需要任何工作。如果使用公共堆,则客户端必须序列化访问。使用锁里面这个函数是不够的:只考虑一个malloc锁定堆*然后一个信号出现并在同一个指针上调用malloc:死锁:信号无法继续,并且客户端也不能因为它被中断。
一般来说,锁不会使线程安全。它们实际上通过不适当地尝试管理客户端拥有的资源来破坏安全性。锁定必须由对象制造商完成,这是唯一知道创建了多少对象以及如何使用它们的代码。
答案 4 :(得分:3)
列出的点中的“共同线程”(双关语!?)是该函数不能做任何会影响对同一函数的任何递归或并发调用行为的事情。
因此,例如静态数据是一个问题,因为它由所有线程拥有;如果一个调用修改了静态变量,则所有线程都使用修改后的数据,从而影响其行为。自修改代码(尽管很少遇到,在某些情况下会被阻止)将是一个问题,因为虽然有多个线程,但只有一个代码副本;代码也是必不可少的静态数据。
基本上可重入,每个线程必须能够使用该函数,就好像它是唯一的用户一样,如果一个线程可以以非确定性的方式影响另一个线程的行为,则情况并非如此。主要是这涉及每个线程具有该函数可用的单独或恒定数据。
所有这一切,第(1)点不一定是真的;例如,您可能合法地并且通过设计使用静态变量来保留递归计数以防止过度递归或分析算法。
线程安全功能不需要是可重入的;它可以通过专门防止锁定的重入来实现线程安全,而点(6)表示这样的函数不是可重入的。关于point(6),调用一个锁定的线程安全函数的函数在递归中使用是不安全的(它会死锁),因此不会说它是可重入的,尽管它可能对并发性是安全的,并且在多个线程可以同时在这样的函数中使用它们的程序计数器(只是没有锁定区域)的意义上,它仍然是可重入的。这可能有助于区分线程安全与重新执行(或者可能增加您的困惑!)。
答案 5 :(得分:1)
您的“其他”问题的答案是“否”,“否”和“否”。仅仅因为函数是递归的和/或线程安全的,它不会使它重入。
这些类型的功能中的每一种都可能在您引用的所有点上失败。 (虽然我不是100%肯定第5点)。
答案 6 :(得分:1)
“线程安全”和“重入”这两个术语仅表示其定义所说的内容。在这种情况下,“安全”意味着仅您在其下面引用的定义。
这里的“安全”当然并不意味着在更广泛的意义上安全,即在给定的上下文中调用给定的函数不会完全影响您的应用程序。总而言之,函数可以在多线程应用程序中可靠地产生期望的效果,但根据定义不符合重入或线程安全的要求。相反,您可以在多线程应用程序中以可能产生各种不希望的,意外的和/或不可预测的效果的方式调用可重入函数。
递归函数可以是任何东西,并且重入函数具有比线程安全更强的定义,因此编号问题的答案都是否定的。
阅读可重入的定义,可以将其概括为一种功能,它不会修改任何超出你所谓的修改的功能。但你不应该只依赖摘要。
在一般情况下,多线程编程只是extremely difficult。知道一个代码的哪个部分可以重新进入只是这个挑战的一部分。螺纹安全性不是附加的。不要试图将可重入函数拼凑在一起,最好使用整体thread-safe design pattern并使用此模式指导您使用每个线程和共享资源。你的计划。
答案 7 :(得分:0)
/* strtok example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] ="- This, a sample string.";
char * pch;
printf ("Splitting string \"%s\" into tokens:\n",str);
pch = strtok (str," ,.-");
while (pch != NULL)
{
printf ("%s\n",pch);
pch = strtok (NULL, " ,.-");
}
return 0;
}