线程安全与可重入

发布于 2024-07-19 07:08:59 字数 183 浏览 12 评论 0原文

最近,我问了一个问题,标题为“malloc线程安全吗?”,其中我问:“malloc 是可重入的吗?”

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

这个假设是错误的吗?

Recently, I asked a question, with title as "Is malloc thread safe?", and inside that I asked, "Is malloc re-entrant?"

I was under the impression that all re-entrant are thread-safe.

Is this assumption wrong?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

善良天后 2024-07-26 07:08:59

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

TL;DR: A function can be reentrant, thread-safe, both or neither.

The Wikipedia articles for thread-safety and reentrancy are well worth reading. Here are a few citations:

A function is thread-safe if:

it only manipulates shared data structures in
a manner that guarantees safe execution by multiple
threads at the same time.

A function is reentrant if:

it can be interrupted at any point during its execution
and then safely called again ("re-entered") before its
previous invocations complete execution.

As examples of possible reentrance, the Wikipedia gives the example of a function designed to be called by system interrupts: suppose it is already running when another interrupt happens. But don't think you're safe just because you don't code with system interrupts: you can have reentrance problems in a single-threaded program if you use callbacks or recursive functions.

The key for avoiding confusion is that reentrant refers to
only one thread executing. It is a concept from the time when
no multitasking operating systems existed.

Examples

(Slightly modified from the Wikipedia articles)

Example 1: not thread-safe, not reentrant

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

Example 2: thread-safe, not reentrant

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

Example 3: not thread-safe, reentrant

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

Example 4: thread-safe, reentrant

/* 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;
}
一梦浮鱼 2024-07-26 07:08:59

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

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

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

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

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

但他们也警告:

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

It depends on the definition. For example Qt uses the following:

  • A thread-safe* function can be called simultaneously from multiple threads, even when the invocations use shared data, because all references to the shared data are serialized.

  • A reentrant function can also be called simultaneously from multiple threads, but only if each invocation uses its own data.

Hence, a thread-safe function is always reentrant, but a reentrant function is not always thread-safe.

By extension, a class is said to be reentrant if its member functions can be called safely from multiple threads, as long as each thread uses a different instance of the class. The class is thread-safe if its member functions can be called safely from multiple threads, even if all the threads use the same instance of the class.

but they also caution:

Note: Terminology in the multithreading domain isn't entirely standardized. POSIX uses definitions of reentrant and thread-safe that are somewhat different for its C APIs. When using other object-oriented C++ class libraries with Qt, be sure the definitions are understood.

甜宝宝 2024-07-26 07:08:59

可重入函数不依赖于 C 库标头中公开的全局变量。以 C 中的 strtok() 与 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;
}

所以,正如您所看到的,让多个线程在没有某种锁定的情况下使用它将是一场灾难..但它没有可重入的目的。 当动态分配内存在某些嵌入式平台上是禁忌时,您就会遇到这种情况。

重入通常并不意味着线程安全,它取决于传递到函数入口点的定义函数或匿名函数的行为、递归等。

在纯函数式编程中,可 safe'对于并发访问是安全的,这更好地说明了需求。

Re-entrant functions do not rely on global variables that are exposed in the C library headers .. take strtok() vs strtok_r() for example in C.

Some functions need a place to store a 'work in progress' , re-entrant functions allow you to specify this pointer within the thread's own storage, not in a global. Since this storage is exclusive to the calling function, it can be interrupted and re-entered (re-entrant) and since in most cases mutual exclusion beyond what the function implements isn't required for this to work, they are often considered to be thread safe. This isn't, however, guaranteed by definition.

errno, however, is a slightly different case on POSIX systems (and tends to be the oddball in any explanation of how this all works) :)

In short, reentrant often means thread safe (as in "use the reentrant version of that function if you're using threads"), but thread safe does not always mean re-entrant (or the reverse). When you're looking at thread-safety, concurrency is what you need to be thinking about. If you have to provide a means of locking and mutual exclusion to use a function, then the function isn't inherently thread-safe.

But, not all functions need to be examined for either. malloc() has no need to be reentrant, it does not depend on anything out of the scope of the entry point for any given thread (and is itself thread safe).

Functions that return statically allocated values are not thread safe without the use of a mutex, futex, or other atomic locking mechanism. Yet, they don't need to be reentrant if they're not going to be interrupted.

i.e.:

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

So, as you can see, having multiple threads use that without some kind of locking would be a disaster .. but it has no purpose being re-entrant. You'll run into that when dynamically allocated memory is taboo on some embedded platform.

In purely functional programming, reentrant often doesn't imply thread safe, it would depend on the behavior of defined or anonymous functions passed to the function entry point, recursion, etc.

A better way to put 'thread safe' is safe for concurrent access , which better illustrates the need.

黯淡〆 2024-07-26 07:08:59

是的。 所有可重入函数都是线程安全的。 可重入函数属于线程安全函数的范畴。

线程安全函数和可重入函数之间的区别是可重入函数必须仅对本地数据进行操作。 虽然线程安全函数可以对共享数据进行操作,但是对此共享数据的访问必须同步。

Yes. All reentrant functions are thread safe. Reentrant functions come under the category of thread-safe functions.

The difference between thread-safe functions and reentrant functions is reentrant functions must operate on only the local data. Where as thread-safe functions can operate on shared data between however access to this shared data must be synchronized.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文