C Thread Safe and Reentrant Function Examples
2021-07-11 17:05:02
C/C++
236

Re-entrance and thread-safety are two different concepts that can be associated with good programming practices. In this article we will try and understand both the concepts and their differences with the help of some code snippets.

1. Thread Safe Code

As the name suggests, a piece of code is thread safe when more than one thread can execute the same code without causing synchronization problems. Lets look at the following code snippet :

...
...
...

char arr[10];
int index=0;

int func(char c)
{
    int i=0;
    if(index >= sizeof(arr))
    {
        printf("\n No storage\n");
        return -1;
    }
    arr[index] = c;
    index++;
    return index;
}

...
...
...

The above function populates the array ‘arr’ with the character value passed to it as argument and then updates the ‘index’ variable so that subsequent calls to this function write on the updated index of the array.

Suppose this function is being used by two threads. Now, lets assume that thread one calls this function and updates the array index with value ‘c’. Now, before updating the ‘index’ suppose second thread gets the execution control and it also calls this function. Now since the index was not updated by thread one , so this thread writes on the same index and hence overwrites the value written by thread one.

So we see that lack of synchronization between the threads was the root cause of this problem.

Now, lets make this function thread safe :

...
...
...

char arr[10];
int index=0;

int func(char c)
{
    int i=0;
    if(index >= sizeof(arr))
    {
        printf("\n No storage\n");
        return -1;
    }

    /* ...
       Lock a mutex here
       ...
    */

    arr[index] = c;
    index++;

    /* ...
       unlock the mutex here
       ...
    */

    return index;
}

...
...
...

What we did above is that we made the array and index updates an atomic operation using the mutex locks. Now even if multiple threads are trying to use this function, there would be no synchronization problems as any thread which acquire the mutex will complete both the operations (array and index update) before any other thread acquires the mutex.

So now the above piece of code becomes thread-safe.

2. Re-entrant Code

The concept of re-entrant code is slightly different from thread safe code. Usually in a single thread of execution if a function is called then before the completion of execution of that particular function, the flow cannot move ahead. But, there are some situations where in a single thread also the execution of a function can be interrupted by a call to same function again. So a piece of code that can successfully handle the this scenario is known as a re-entrant code. Lets look at the example below :

...
...
...

char *s;

void func()
{
    int new_length = 0;

    // initialize 'new_length'
    // with some new value here

    char *ptr = realloc(s, new_length);

    if(ptr)
    {
        s = ptr;
    }
    else
    {
        //Report Failure
    }

    // do some stuff here
}

...
...
...

if we analyze the re-entrance capability of the above code, we find that this code is not re-entrant. This is because of the fact that the above code is buggy in the sense that if the same function is being used by a signal handler (in response to handling of some signals) then in the situation where a call to function func() was between realloc() and the ‘if’ condition next to it and then this execution is interrupted by a call to this function from signal handler. In this scenario since ‘s’ is not updated with new allocated address so realloc might fail (or program might even crash).

So we see that the above code is not re-entrant. A re-entrant code is least expected to work with global variables. Following is an example of a re-entrant code:

...
...
...

int exchange_values(int *ptr1, int *ptr2)
{
    int tmp;

    tmp = *ptr1;
    *ptr1 = *ptr2;
    *ptr2 = *tmp;

    return 0;
}

...
...
...

3. Thread Safe but not Re-entrant

A piece of code can be thread safe but it’s not necessary that its re-entrant. Look at the following code :

...
...
...

int func()
{
    int ret = 0;

    // Lock Mutex here

    // Play with some
    // global data structures
    // here   

    // Unlock mutex

    return ret;
}

...
...
...

In the example above, since the critical section is protected by mutex so the code above is thread safe but its not re-entrant because if the execution of the above function is interrupted through some signal handler (calling the same function while handling a signal) then (if non-recursive mutexes are being used) the first execution is interrupted while the second execution will wait forever to acquire mutex. So overall the complete program will hang.

4. Re-entrant but not Thread Safe

Re-entrance is something which is associated with a function whose first execution gets interrupted by second call to it (from within the same thread) and this first execution resumes when the second execution completes. This is not the case with threads which can keep on stepping onto another thread’s toes multiple times. So if a function is re-entrant then it does not guarantee that its thread safe.