Question

I was looking into the old concept of writing re-enterant code. They say, don't use global, static variables. Because, it is prone to non-deterministic behaviour. However, I am not sure about whether it is applicable where there is a single thread. I understand thread safe and reentrancy are two different concepts. While using C++, we avoid using global and static variables. Even, we try to avoid singletons. But, the question is what will happen to his piece of code borrowed from the wiki, if it is running on a single thread. Is it prone to non-deterministic behaviour.

http://en.wikipedia.org/wiki/Reentrancy_%28computing%29

int g_var = 1;

int f()
{
    g_var = g_var + 2;
    return g_var;
}

int g()
{
    return f() + 2;
} 

Another part is people say that re-entrancy and thread safe are unrelated. However, in this example we can make the results predictable by putting a mutex in function g. So, when two threads execute this g() concurrently the results are deterministic. So, we actually fixed the non-enterant code by putting a thread safe mechanism. Please clear my concepts here. Am I missing something or my understanding is not right?

Was it helpful?

Solution

Reentrancy problems have more to do with e.g. shared libraries than threads. Remember that shared libraries are shared, there is only one copy of a shared library loaded, no matter how many processes use the library. This of course means that global and static data is shared as well, which can lead to problems. And normal in-process mechanisms like thread mutexes will not help here, since those are only per process, you have to use inter-process facilities to protect this data. And doing it is often too much work, it's often easier to avoid the issue completely by not having global or static data in shared libraries.

OTHER TIPS

Note that this program will be compiled to something like this:

int g_var = 1;

int f()
{
    int tmp = gvar;
    // hardware interrupt might invoke isr() here!
    tmp += 2;
    g_var = tmp;
    return tmp;
}

int g()
{
    int tmp = f();
    return tmp + 2;
}

So if f() is interrupted in the middle and reentered, the two invocations of f() will increase g_var by only 2 and not 4.

You must take care in the following situations:

  • Multi-threaded programs.
  • Multi-process programs where several processes share the same variables.
  • Programs using hardware interrupts and interrupt service routines.
  • Programs using certain forms of callback functions.

The code you posted uses none of the above so it will work perfectly fine.

Another part is people say that re-entrancy and thread safe are unrelated.

Re-entrant usually means "this code is written in such a manner, that it needs no protection mechanisms". For example, a function that only uses local variables and no library calls is re-entrant.

Thread-safe means that the code is properly using mechanisms such as mutexes or critical sections to protect shared resources.

The opposite of re-entrant is non-re-entrant. And when you fix non-re-entrant code, you make it thread-safe. So the terms are related but not synonymous: they mean different things.

Reentrancy problems are not unique to multi-threaded code.

Consider the following (single thread) code:

time_t t = time(NULL);
struct tm *a;
struct tm *b;

a = localtime(&t);
t += 3600;
b = localtime(&t);

Here you have an issue, since localtime() is not reentrant. It (usually) returns a pointer to static storage. Thus while the struct tm a and b pointers should have different content, they're now the same. a == b, since the last call would change the same struct that a points to.

The above code can be fixed for a single thread program like so:

time_t t = time(NULL);
struct tm *tmp;
struct tm a;
struct tm b;

tmp = localtime(&t);
a = *tmp;
t += 3600;
b = localtime(&t);
b = *tmp;

For multi threaded programs another set of problems are introduced by non-reentrant functions, as multiple threads could call localtime() at the same time, leading to unpredictable race conditions. Now, some platforms that supports threads implements localtime() and other non-reentrant functions using thread-local storage, reducing the problem to be similar to that of a single threaded program.

Reentrancy problems related to multi threading can normally be solved with a mutex. To safely use localtime(), you could create your own function that protects calling localtime() and copying out the result:

static mutex lmutex;
struct my_localtime(time_t *t, struct tm *result)
{
    struct tm *tmp;

    mutex_lock(&lmutex);
    tmp = localtime(tmp);
    *result = *tmp;
    mutex_unlock(&lmutex);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top