我知道这个论坛已经讨论了很多。 但有一件事让我感到困惑。 维基百科提到每个重入代码都是线程安全的。 http://en.wikipedia.org/wiki/Reentrant_%28subroutine%29 后来给出了一个函数的例子,它是一个重入但不是线程安全的函数。
int t;
void swap(int *x, int *y)
{
int s;
s = t; // save global variable
t = *x;
*x = *y;
// hardware interrupt might invoke isr() here!
*y = t;
t = s; // restore global variable
}
void isr()
{
int x = 1, y = 2;
swap(&x, &y);
}
这让我很困惑。所有重入代码都是安全的吗? 此外,所有递归函数线程都是安全的。 我无法想象这种差异。
由于
答案 0 :(得分:2)
重入和线程安全的概念是相关的,但并不等同。您可以编写一个非线程安全的重入函数,以及一个不可重入的线程安全函数。我将使用C#作为我的例子:
非线程安全的重入函数
此函数反转数组的条目:
void Reverse(int[] data) {
if (data == null || data.Length < 2) return;
for (var i = 0 ; i != data.Length/2 ; i++) {
int tmp = data[i];
data[i] = data[data.Length-i-1];
data[data.Length-i-1] = tmp;
}
}
此功能显然是可重入的,因为它不引用外部资源。但是,如果没有从多个线程传递相同的data
,那么它的线程安全性是条件。如果多个线程同时传递Reverse
相同的数组实例,则可能会产生不正确的结果。使这个函数无条件地线程安全的一种方法是添加一个锁:
void Reverse(int[] data) {
if (data == null || data.Length < 2) return;
lock (data) {
for (var i = 0 ; i != data.Length/2 ; i++) {
int tmp = data[i];
data[i] = data[data.Length-i-1];
data[data.Length-i-1] = tmp;
}
}
}
非重新进入的线程安全功能
此函数调用函数f()
c
次,并返回返回的值多于其他值的值。
static int[] counts = new int[65536];
unsigned short MaxCount(Func<unsigned short> f, int c) {
lock(counts) {
Array.Clear(counts, 0, counts.Length);
for (var i = 0 ; i != c ; i++) {
counts[f()]++;
}
unsigned short res = 0;
for (var i = 1 ; i != counts.Length ; i++) {
if (counts[i] > counts[res]) {
res = i;
}
}
return res;
}
}
此函数是线程安全的,因为它锁定了用于进行计数的静态数组。但是,它不是可重入的:例如,如果传入的仿函数f
要调用MaxCount
,则会返回错误的结果。
答案 1 :(得分:1)
所有重入代码是否安全?
没有
换句话说,重入的概念对于多线程执行(最常见的例子)是遗忘的。只有当这是你系统中的实际约束时,那么是,reentrant将符合线程安全的条件(但是在该环境中也只有一个线程)。
IMO,术语“可重入”只能在合格系统的上下文中使用(不适用于您的桌面操作系统,但适用于某些嵌入式系统)。
现在,如果你真的想强制在多线程环境中使用“reentrance”这个词:只有在你还保证它也是线程安全的情况下,你才可以在多线程系统中强制重入。或许更好的问题是“为了保证在多线程上下文中重入,这是否意味着函数及其引用的所有数据也需要线程安全?” - 答:是的,函数及其引用的所有内容也需要线程安全且可重入,以使函数在该上下文中可重入。快速实现这一点变得复杂,并且是全局变量不是一个好主意的原因。
此外,所有递归函数线程都是安全的。
没有
答案 2 :(得分:0)
该函数不是线程安全的,因为它访问函数作用域之外的资源t
(未在堆栈上分配)而没有任何保护机制(例如锁定)以确保对{{1}的访问是原子的。
维基百科页面实际上指出:
可重入子程序可以实现线程安全,但这种情况 在所有情况下,单独可能不够。
(我的重点)。
更新:
可重入(在本文中定义)只是意味着单个执行线程(想想DOS)可以“将指令指针”从执行例程的中间“删除”,将其放在其他位置,并继续执行线性流程新代码(例如DOS天中的中断子程序),并且相同的线性流可以再次返回到函数中。在这种有限的情况下,函数的第二次调用必须在控制从中断子程序转移回“常规”程序执行之前完成,“常规”程序执行将在指令指针最初被撕掉的例程中的相同点处恢复。此方案不允许任意线程调度,而是允许中断将控制切换到新点,然后完成,然后在中断点恢复。
请注意,在现实生活中,事情可能并非如此简单。我不再完全确定(已经...... 20年了?),但我认为一个中断例程可以中断和中断正在进行的例程(例如,软调试器中断可以中断定时器中断等)。
答案 3 :(得分:0)
函数“swap”不是可重入的,因为它使用全局状态(对于whit,全局变量“t”)。
答案 4 :(得分:0)
如果您的子程序不影响外部变量(没有副作用),您可以说是可重入的。虽然这不是一个经验法则,但它是一个很好的指导方针 如果子程序使用外部变量并在开始时保存外部变量的状态并在结束时恢复该状态,那么子程序是可重入的。
如果一个子程序修改了外部变量并且它没有先保存它们的状态,并且它被中断,那么这些外部变量的状态就可以改变,因此当调用返回到子程序中的原始位置时,外部变量不同步,导致子程序的状态不一致。
可重入函数保存它们的状态(在它们的本地堆栈上,在线程堆栈中不使用全局变量)。
在您的情况下,您访问外部变量t
,但它是可重入的,因为变量在子例程结束时保存并恢复。
通常线程安全意味着重入,但同样不再是规则的更多规则。 注意: java中的某些锁是可重入的,因此您可以递归调用该方法,而不会被之前的调用阻止。 (在此上下文中重新进入意味着线程可以访问使用相同锁定锁定的任何部分 - 线程可以重新输入已经拥有锁定的任何代码块)
<强>解决方案/答案:强>
如果用atomics保护t
,你应该得到一个线程安全的子程序。此外,如果在每个线程的堆栈上放置t
(使其成为本地),则子例程变为线程安全,因为没有全局数据。
另外,reentrant!=递归;你也可以在递归子程序中执行一个ISR。线程方面,如果从2个线程调用递归子例程,则会产生垃圾。要使线程安全,请使用可重入锁保护递归子例程(其他非可重入锁将导致死锁/阻塞)。