Threadsafe vs re-entrant
Asked Answered
A

3

108

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?

Abernon answered 13/5, 2009 at 8:43 Comment(0)
C
47

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.

Crippen answered 13/5, 2009 at 8:55 Comment(6)
Reentrant does not imply thread-safe. Pure functions imply thread-safety.Blondell
Great answer Tim. Just to clarify, my understanding from your "often" is that thread-safe doesn't imply reentrant, but also reentrant doesn't imply thread-safe. Would you be able to find an example of a reentrant function which is not thread-safe?Libbey
@ Tim Post "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." qt says opposite: "Hence, a thread-safe function is always reentrant, but a reentrant function is not always thread-safe."Resnatron
and wikipedia says yet something another: "This definition of reentrancy differs from that of thread-safety in multi-threaded environments. A reentrant subroutine can achieve thread-safety,[1] but being reentrant alone might not be sufficient to be thread-safe in all situations. Conversely, thread-safe code does not necessarily have to be reentrant (...)"Resnatron
@Riccardo: Functions synchronized through volatile variables but not full memory barriers for use with signal/interrupt handlers are usually re-entrant but thread-safe.Matronly
@Libbey Consider a re-entrant function that accesses a shared resource. Being re-entrant, the function is well-behaved if two threads call it at the same time. But, if some other function can also access the shared resource at the same time, neither function is thread safe.Iritis
K
122

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;
}
Khalkha answered 30/10, 2015 at 22:34 Comment(12)
I know I'm not supposed to comment just to say thanks, but this is one of the best illustrations laying out the differences between re-entrant and thread safe functions. In particular you have used very concise clear terms, and chose a great example function to distinguish between the 4 categories. So, Thanks!Lempres
A function is both reentrant and thread-safe if it doesn't use any global /static var. Thread - safe: when many threads runs your function at the same time, is there any race?? If you use global var, use lock to protect it. so it is thread-safe. reentrant: if a signal occurs during your function execution, and call your function in the signal again, is it safe??? in such case, there is no multiple threads. you shall not use any static/global var to make it reentrant...Bradbradan
It seems to me thay exemple 3 is not reentrant: if a signal handler, interrupting after t = *x, calls swap(), then t will be overridden, leading to unexpected results.Judicatory
@Khalkha how is example 3 reentrant?Jupon
@SandBag_1996, let's consider a call to swap(5, 6) being interrupted by a swap(1, 2). After t=*x, s=t_original and t=5. Now, after the interruption, s=5 and t=1. However, before the second swap returns it will restore context, making t=s=5. Now, we go back to the first swap with t=5 and s=t_original and continue after t=*x. So, the function does appear to be re-entrant. Remember that every call gets its own copy of s allocated on stack.Dyun
@Dyun Your example is hard to understand, probably because of lot of words and my inability to imagine it at this hour, but you said something along the lines that before the second swap returns it will restore context, based on last assignment, t=s, right? But the context switch can happen anytime, so what if it is returned before that, now t is changed, *y gets different value. Basic rule of reentrancy is, not to muck around with global without mutex protection.Jupon
@Jupon The assumption is that if the function gets interrupted (at any point), it's only to be called again, and we wait until it completes before continuing the original call. If anything else happens, then it's basically multithreading, and this function is not thread-safe. Suppose the function does ABCD, we only accept things like AB_ABCD_CD, or A_ABCD_BCD, or even A__AB_ABCD_CD__BCD. As you can check, example 3 would work fine under these assumptions, so it is reentrant. Hope this helps.Khalkha
@SandBag_1996, mutex would actually make it non-reentrant. First invocation locks mutex. In comes second invocation - deadlock.Dyun
@SandBag_1996, what do you mean context switch can happen any time? CPU should guarantee observable order.Dyun
@Dyun I recommend some basic multi-threading tutorials, and some readings on how a deadlock actually works.Jupon
We need a lock in example 3's function.Pandemonium
@Jupon you said "if a signal handler, interrupting after t = *x, calls swap(), then t will be overridden, leading to unexpected results" - yes, 't' will be overridden but that won't affect the final outcome of the swap and the result will be correct (i.e. there will be no 'unexpected results').Levins
D
61

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.

Dace answered 13/5, 2009 at 9:8 Comment(2)
This definition of reentrant is too strong.Ullund
A function is both reentrant and thread-safe if it doesn't use any global /static var. Thread - safe: when many threads runs your function at the same time, is there any race?? If you use global var, use lock to protect it. so it is thread-safe. reentrant: if a signal occurs during your function execution, and call your function in the signal again, is it safe??? in such case, there is no multiple threads. It's best that your don't use any static/global var to make it reentrant, or like in example 3.Bradbradan
C
47

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.

Crippen answered 13/5, 2009 at 8:55 Comment(6)
Reentrant does not imply thread-safe. Pure functions imply thread-safety.Blondell
Great answer Tim. Just to clarify, my understanding from your "often" is that thread-safe doesn't imply reentrant, but also reentrant doesn't imply thread-safe. Would you be able to find an example of a reentrant function which is not thread-safe?Libbey
@ Tim Post "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." qt says opposite: "Hence, a thread-safe function is always reentrant, but a reentrant function is not always thread-safe."Resnatron
and wikipedia says yet something another: "This definition of reentrancy differs from that of thread-safety in multi-threaded environments. A reentrant subroutine can achieve thread-safety,[1] but being reentrant alone might not be sufficient to be thread-safe in all situations. Conversely, thread-safe code does not necessarily have to be reentrant (...)"Resnatron
@Riccardo: Functions synchronized through volatile variables but not full memory barriers for use with signal/interrupt handlers are usually re-entrant but thread-safe.Matronly
@Libbey Consider a re-entrant function that accesses a shared resource. Being re-entrant, the function is well-behaved if two threads call it at the same time. But, if some other function can also access the shared resource at the same time, neither function is thread safe.Iritis

© 2022 - 2024 — McMap. All rights reserved.