线程安全与重入

最近,我问了一个问题,题目是“malloc线程安全吗? ,在里面我问:“malloc是可重入的吗?

我的印象是所有重入者都是线程安全的。

这个假设是错误的吗?

重入函数不依赖于C库头文件中公开的全局variables。例如,在C中使用strtok()和strtok_r()。

一些函数需要一个地方来存储“进行中的工作”,重入函数允许你在线程自己的存储中指定这个指针,而不是在全局中。

但是,在POSIX系统上,errno是一个稍微不同的情况:)

简而言之,可重入通常意味着线程安全(例如,如果使用线程,则使用该函数的可重入版本),但线程安全并不总是意味着可重入。 有些函数不依赖于其他线程可能会暴露的暴露的全局variables。

malloc()不需要是可重入的,它不依赖任何给定线程的入口点范围之外的任何东西。

没有使用互斥体,futex或其他primefaceslocking机制,返回静态分配值的函数不是线程安全的。 然而,他们不需要是可重入的。

即:

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; } 

所以,正如你所看到的,有多个线程使用,没有某种locking将是一场灾难..但它没有用途重入。 在某些embedded式平台上,当dynamic分配的内存被禁止时,会遇到这种情况。

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

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

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

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

  • 可重入函数也可以从多个线程同时调用,但前提是每个调用使用自己的数据。

因此,一个线程安全函数总是可重入的,但是一个可重入函数并不总是线程安全的。

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

但他们也谨慎:

注意:multithreading领域中的术语尚未完全标准化。 POSIX使用可重入和线程安全的定义,它们的C API有些不同。 在Qt中使用其他面向对象的C ++类库时,请确保了解定义。

TL; DR:一个函数可以是可重入的,线程安全的,两者都不是。

维基百科关于线程安全和重入的文章非常值得一读。 这里有几个引用:

一个函数是线程安全的,如果:

它只能以保证multithreading同时安全执行的方式操作共享数据结构。

一个函数是可重入的,如果:

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

作为可能的再入口的例子,维基百科给出了一个被devise为由系统中断调用的函数的例子:假设在另一个中断发生时它已经在运行。 但是,不要因为没有使用系统中断编码就认为你是安全的:如果你使用callback函数或者recursion函数,你可以在单线程程序中存在再入口问题。

避免混淆的关键是可重入是指只有一个线程正在执行。 这是一个从没有多任务操作系统存在的概念。

例子

(从维基百科文章略微修改)

示例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; }