The difference between thread-safety and re-entrancy

Date:June 29, 2004 / year-entry #258
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20040629-00/?p=38643
Comments:    19
Summary:An operation is "thread-safe" if it can be performed from multiple threads safely, even if the calls happen simultaneously on multiple threads. An operation is re-entrant if it can be performed while the operation is already in progress (perhaps in another context). This is a stronger concept than thread-safety, because the second attempt to perform...

An operation is "thread-safe" if it can be performed from multiple threads safely, even if the calls happen simultaneously on multiple threads.

An operation is re-entrant if it can be performed while the operation is already in progress (perhaps in another context). This is a stronger concept than thread-safety, because the second attempt to perform the operation can even come from within the same thread.

Consider the following function:

int length = 0;
char *s = NULL;

// Note: Since strings end with a 0, if we want to
// add a 0, we encode it as "\0", and encode a
// backslash as "\\".

// WARNING! This code is buggy - do not use!

void AddToString(int ch)
{
  EnterCriticalSection(&someCriticalSection);
  // +1 for the character we're about to add
  // +1 for the null terminator
  char *newString = realloc(s, (length+1) * sizeof(char));
  if (newString) {
    if (ch == '\0' || ch == '\\') {
      AddToString('\\'); // escape prefix
    }
    newString[length++] = ch;
    newString[length] = '\0';
    s = newString;
  }
  LeaveCriticalSection(&someCriticalSection);
}

This function is thread-safe because the critical section prevents two threads from attempting to add to the string simultaneously. However, it is not re-entrant.

The internal call to AddToString occurs while the data structures are unstable. At the point of the call, execution re-enters the start of the function AddToString, but this time the attempt to realloc the memory will use a pointer (s) that is no longer valid. (It was invalidated by the call to realloc performed by the caller.)


Comments (19)
  1. Serge Wautier says:

    2nd reason why it’s not re-entrant is that it would ‘loop’ forever when ch== 0 or

  2. Cooney says:

    Nah, eventually it’ll crash in some difficult to debug way.

  3. orcmid says:

    Hi Raymond, I like your clean difference between re-entrant and thread-safe. Thanks.

  4. Raymond Chen says:

    (Rats, missed the infinite loop on . Okay, so remove the test against and then you’re set.)

  5. And of course re-entrancy is an interesting issue with STA COM components, especially as if you don’t think you have to worry about it ‘cos you’re in an STA ;)

  6. Callek says:

    I can only assume that "someCriticalSection" is a global variable of some sort, whereas quite obviously, "EnterCriticalSection" blocks re-entrancy from other threads.

    It is fairly obvious but not quite as obvious as it could be.

  7. Sea Urchin says:

    Do I understand it correctly if I believe that re-entrant being stronger than thread-safe means that a re-entrant operation is automagically thread-safe?

    Or can an operation be re-entrant and not thread-safe at the same time?

  8. asdf says:

    A function can be reentrant but not thread safe. There are two types of reentrancy issues, if it is reentrant by the same thread only or if it is reentrant by multiple threads. The latter of the two is the strongest one (and makes it automagically thread safe). For example:

    void foo(unsigned i)

    {

    if (i–) {

    free(malloc(1));

    foo(i);

    }

    }

    If malloc and free access a global heap and are not protected by any locks, this function is reentrant but not multiple-thread reentrant.

  9. Keith Moore [exmsft] says:

    Another source of re-entrancy is user-mode APCs. Any routine that enters an alertable wait (calls SleepEx() or WaitForXxxxEx() with Alertable == TRUE) can potentially cause queued APCs to run.

    Consider:

    ReadFileEx( …, &MyCompletionRoutine );

    SomethingThatWaitsAlertably();

    /* etc */

    MyCompletionRoutine()

    {

    SomethingThatWaitsAlertably();

    }

  10. Pavel Lebedinsky says:

    This is one of the reasons why critical sections, mutexes etc. shouldn’t allow recursive locks (at least not by default).

    A deadlock is always better than undefined behavior.

  11. josh says:

    Even if this were reentrant, it wouldn’t work. It’s impossible to have length correct for the AddToString(‘\’) at that point.

    This is reentrant (assuming it’s never called from an interrupt) but not thread safe:

    void AddToString(int ch)

    {

    char *newString;

    if (ch == 0)

    AddToString(‘\’);

    newString = realloc(s, (length+1) * sizeof(char))

    if (newString)

    {

    newString[length++] = ch;

    newString[length] = 0;

    s = newString;

    }

    }

  12. Is reentrancy mostly related to recursive functions? What are other scenarios besides alertable waits and interrupts?

  13. asdf says:

    A really common reentrant function that isn’t recursive is a window’s wndproc. The most common case besides sending messages back and forth between windows is if you spawn something that uses it’s own message loop like a modal dialog or popup window calls your wndproc while you’re already in your wndproc. Only if you’re doing something really dumb can you have problems with that case.

    If you put asserts (the kind that spawns message boxes) on certain messages in your wndproc, your program will just suddenly disapear because of stack overflow.

  14. You seem to suggest that re-entrant implies thread-safe. This is not true.

  15. Jonathan Allen says:

    "You seem to suggest that re-entrant implies thread-safe. This is not true."

    Example please.

  16. Chan Kar Heng says:

    Re-entrant but not thread-safe..

    char * strcopy(char * dst,const char * src)

    {

    assert(dst!=NULL);

    assert(src!=NULL);

    if(*src!=’’)

    strcopy(dst+1,src+1);

    *dst=*src;

    return(dst);

    }

  17. laughingcynic says:

    This is thread-safe.

    If you are going to bring up the example of 2 threads passing in the same dst, then the callers are behaving in an unsafe manner by failing to protect a shared buffer. The flaw is not with this routine.

  18. Raymond Chen says:

    Right, which demonstrates that the routine is not thread-safe and callers must respect that.

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index