线程安全与可重入


Answers:


42

可重入函数不依赖于C库头中公开的全局变量。.例如,在C中以strtok()vs strtok_r()为例。

有些函数需要在某个位置存储“正在进行的工作”,可重入函数允许您在线程自己的存储中而不是在全局中指定此指针。由于此存储是调用函数所独有的,因此可以中断并重新输入(重新进入),并且由于在大多数情况下,此功能不需要执行该函数实现的互斥,因此通常认为它们是线程安全。但是,按定义不能保证。

但是,errno在POSIX系统上的情况略有不同(并且在所有这些如何工作的任何解释中,它都是奇怪的东西):)

简而言之,可重入通常意味着线程安全(如“如果您正在使用线程,请使用该函数的可重入版本”),但是线程安全并不总是意味着可重入(或相反)。当您考虑线程安全时,并发就是您需要考虑的问题。如果必须提供一种锁定和互斥的方法来使用某个函数,则该函数本质上并不是线程安全的。

但是,并非所有功能都需要检查。malloc()不需要重入,它不依赖任何给定线程的入口点范围之外的任何东西(并且它本身是线程安全的)。

如果不使用互斥体,futex或其他原子锁定机制,则返回静态分配值的函数将不是线程安全的。但是,如果他们不会被打扰,则无需重新进入。

即:

static char *foo(unsigned int flags)
{
  static char ret[2] = { 0 };

  if (flags & FOO_BAR)
    ret[0] = 'c';
  else if (flags & BAR_FOO)
    ret[0] = 'd';
  else
    ret[0] = 'e';

  ret[1] = 'A';

  return ret;
}

因此,如您所见,让多个线程使用该线程而不进行某种锁定将是一场灾难..但是重新进入没有任何目的。当某些嵌入式平台上动态分配内存成为禁忌时,您会遇到这种情况。

在纯函数式编程中,可重入通常并不意味着线程安全,它取决于传递给函数入口点,递归等的已定义或匿名函数的行为。

更好的“线程安全”方法对于并发访问安全的,这更好地说明了这一需求。


2
可重入并不意味着线程安全。纯函数意味着线程安全。
朱利奥·格拉

很好的答案,蒂姆。需要澄清的是,我从您的“经常”的理解是线程安全并不意味着可重入,但可重入也不意味着线程安全。您是否可以找到不是线程安全的可重入函数的示例?
Riccardo 2014年

@ Tim Post“简而言之,可重入通常意味着线程安全(如“如果您正在使用线程,请使用该函数的可重入版本”),但是线程安全并不总是意味着可重入。” qt表示相反:“因此,线程安全函数始终是可重入的,但是可重入函数并不总是线程安全的。”
2015年

维基百科又说了一句:“重入的定义不同于多线程环境中的线程安全。重入子例程可以实现线程安全,[1]但仅重入子例程可能不足以保证线程安全相反,线程安全代码不一定必须是可重入的(...)“
4pie0

@Riccardo:通过易失性变量而不是与信号/中断处理程序一起使用的完整内存屏障同步的函数通常是可重入但线程安全的。
doynax 2015年

77

TL; DR:函数可以是可重入的,线程安全的,或者两者均可。

维基百科上有关线程安全性可重入性的文章非常值得一读。以下是一些引用:

在以下情况下,函数是线程安全的

它仅以确保多个线程同时安全执行的方式操作共享数据结构。

在以下情况下,函数是可重入的

它可以在执行期间的任何时候中断,然后在之前的调用完成执行之前安全地再次调用(“重新输入”)。

作为可能重新进入的示例,维基百科给出了旨在由系统中断调用的功能的示例:假设在发生另一个中断时该功能已经在运行。但是不要仅仅因为您没有使用系统中断进行编码就认为您很安全:如果使用回调或递归函数,则在单线程程序中可能会遇到重入问题。

避免混淆的关键是可重入仅指一个正在执行的线程。从不存在多任务操作系统的时代开始就是一个概念。

例子

(从维基百科的文章稍作修改)

示例1:不是线程安全的,不是可重入的

/* As this function uses a non-const global variable without
   any precaution, it is neither reentrant nor thread-safe. */

int t;

void swap(int *x, int *y)
{
    t = *x;
    *x = *y;
    *y = t;
}

示例2:线程安全,不可重入

/* We use a thread local variable: the function is now
   thread-safe but still not reentrant (within the
   same thread). */

__thread int t;

void swap(int *x, int *y)
{
    t = *x;
    *x = *y;
    *y = t;
}

示例3:不是线程安全的,可重入

/* We save the global state in a local variable and we restore
   it at the end of the function.  The function is now reentrant
   but it is not thread safe. */

int t;

void swap(int *x, int *y)
{
    int s;
    s = t;
    t = *x;
    *x = *y;
    *y = t;
    t = s;
}

示例4:线程安全,可重入

/* We use a local variable: the function is now
   thread-safe and reentrant, we have ascended to
   higher plane of existence.  */

void swap(int *x, int *y)
{
    int t;
    t = *x;
    *x = *y;
    *y = t;
}

10
我知道我不应该只说谢谢就发表评论,但这是最好的例证之一,它阐明了可重入函数与线程安全函数之间的区别。特别是,您使用了非常简洁明了的术语,并选择了一个出色的示例函数来区分这4个类别。那谢谢啦!
ryyker '17

11
在我看来,示例3并非可重入:如果信号处理程序在,之后t = *x调用swap(),中断,t则将被覆盖,从而导致意外结果。
rom1v

1
@ SandBag_1996,让我们考虑一个swap(5, 6)被中断的调用swap(1, 2)。之后t=*xs=t_originalt=5。现在,在中断之后,s=5t=1。但是,在第二个swap返回之前,它将恢复上下文,即t=s=5。现在,我们回到第一个swapt=5 and s=t_original然后继续t=*x。因此,该函数确实是可重入的。请记住,每个调用都会获得自己s分配的堆栈副本。
urnonav

4
@ SandBag_1996假设是,如果该函数(在任何时候)被中断,则只能再次调用它,我们等待直到完成为止,然后再继续原始调用。如果发生任何其他情况,则基本上是多线程,并且此函数不是线程安全的。假设函数执行ABCD,我们只接受AB_ABCD_CD或A_ABCD_BCD,甚至A__AB_ABCD_CD__BCD之类的东西。您可以检查一下,在这些假设下示例3可以正常工作,因此它是可重入的。希望这可以帮助。
MiniQuark

1
@ SandBag_1996,互斥锁实际上会使它不可重入。第一次调用将锁定互斥锁。在第二次调用-死锁。
urnonav

56

这取决于定义。例如,Qt使用以下内容:

  • 即使调用使用共享数据,也可以从多个线程同时调用线程安全*函数,因为对共享数据的所有引用都已序列化。

  • 一个可重入函数也可以从多个线程同时调用,但只有当每个调用使用自己的数据。

因此,线程安全功能始终是可重入的,但可重入功能并非始终是线程安全的。

通过扩展,如果可以从多个线程安全地调用其成员函数,则该类称为可重入的,只要每个线程使用该类的不同实例即可。如果可以从多个线程安全地调用其成员函数,则该类是线程安全的,即使所有线程都使用该类的相同实例也是如此。

但他们也警告:

注意:多线程域中的术语尚未完全标准化。POSIX使用可重入和线程安全的定义,这些定义对其C API有所不同。当将其他面向对象的C ++类库与Qt一起使用时,请确保了解定义。


2
可重入的定义太强了。
qweruiop

如果函数不使用任何全局/ static var,则它既是可重入的又是线程安全的。线程-安全:当多个线程同时运行您的函数时,会发生任何争执吗?如果使用全局变量,请使用锁来保护它。因此它是线程安全的。可重入:如果在函数执行期间发生信号,然后再次在信号中调用函数,这是否安全???在这种情况下,没有多个线程。最好不要使用任何静态/全局变量使其重入,或者像示例3中那样
keniee面包车
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.