线程安全与重入

Dee*_*ain 5 multithreading operating-system

我知道这个论坛已经讨论了很多。但有一件事仍然让我感到困惑。维基百科提到每个可重入代码都是线程安全的。 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);
}
Run Code Online (Sandbox Code Playgroud)

这让我很困惑。所有重入代码都是线程安全的吗?此外,所有递归函数都是线程安全的。我无法想象差异。

谢谢

das*_*ght 5

可重入和线程安全的概念是相关的,但并不等同。您可以编写一个非线程安全的可重入函数,以及一个不可重入的线程安全函数。我将使用 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这个函数显然是可重入的,因为它不引用外部资源。然而,它的线程安全性是以不从多个线程传递相同的内容为条件的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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

不可重入的线程安全函数

该函数调用函数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;
    }
}
Run Code Online (Sandbox Code Playgroud)

该函数是线程安全的,因为它锁定用于计数的静态数组。但是,它是不可重入的:例如,如果f传入的函子是 call MaxCount,则会返回错误的结果。

  • “这个函数显然是可重入的”——如果它被中断并且中断处理程序将相同的数组传递给它,会发生什么?向这样的例程添加锁可能会带来灾难性的后果,因为锁通常不可用于中断处理程序/驱动程序 - 操作系统可能会出现双重故障停止或 BSOD。关键是,如果您使用“通过引用传递相同参数”的说法来表示函数不是线程安全的,那么也意味着该函数是不可重入的。 (2认同)

Eri*_* J. 0

该函数不是线程安全的,因为它访问t函数范围之外的资源(未在堆栈上分配),没有任何保护机制(例如锁)来确保访问t是原子的。

维基百科页面实际上指出:

可重入子例程可以实现线程安全,但仅此条件可能不足以满足所有情况。

(我的重点)。

更新:

可重入(如本文中所定义)只是意味着单个执行线程(想想 DOS)可以从执行例程的中间“剥离指令指针”,将其放在其他地方,并继续通过新代码的线性流程(例如,DOS 时代的中断子例程),并且相同的线性流程可以第二次返回到该函数。在这种有限的情况下,函数的第二次调用必须在控制权从中断子例程转移回“常规”程序执行之前完成,“常规”程序执行将在例程中指令指针最初被删除的同一点处恢复。此方案不允许任意线程调度,而是允许将控制权切换到新点的中断,然后该中断完成并恢复到中断点。

请注意,在现实生活中,事情可能没那么简单。我不再完全确定了(已经……20年了?),但我认为一个中断例程可以中断并且已经在进行中的中断例程(例如软调试器中断可以中断定时器中断等)。