These two are very different concepts but can be confusing. re-entrant is used to describe a function in a single-threaded environment, thread-safe in multi-threaded environment on the other hand. A function can be both re-entrant and thread-safe, either re-entrant or thread-safe, or neither.
A function is re-entrant, if it can be interrupted in the middle of an execution, and it's safe to call the same function again in the same thread. The second execution of the function can finish after the first one. Notice how this differs from recursing a function, as in recursion, the latter execution always finishes before the former execution. Re-entering a function is a generalization of recursing a function.
A function is thread-safe, if multiple threads can execute the same function at the same time safely.
Examples
Not thread-safe, not re-entrant
int tmp;
int add10(int a) {
tmp = a;
return tmp + 10; // <--- interrupt here
}
It's not thread-safe because there's a data-race because multiple threads can access tmp
at the same time. It's not re-entrant because for example:
- Call
add10(1)
. - On the line of return, there is an interrupt, and the signal handler calls
add10(2)
. - The
add10(2)
call sets global variabletmp
to2
. - Now after the signal handler, the first call
add10(1)
resumes and returns12
instead of11
. Wrong!
Not thread-safe, but re-entrant
int tmp;
int add10(int a) {
tmp = a;
return a + 10;
}
It's en-entrant. But it's not thread-safe because the data-race on the shared variable tmp
. This is a silly example as tmp
does nothing other than creating a data-race here. But you get the idea.
Thread-safe but not re-entrant
thread_local int tmp;
int add10(int a) {
tmp = a;
return tmp + 10;
}
It's thread-safe thanks to thread_local
. But it's not re-entrant just like the first example.
Thread-safe and re-entrant
int add10(int a) {
return a + 10;
}
Why not thread-safe and re-entrant for all?
It's possible to make all functions thread-safe
. But it comes with performance cost. It's not even possible to make all functions re-entrant. E.g. most of the functions in libevent
are not re-entrant. As long as a function accesses memories out of its stack, it can be non-reentrant. And there are legit reasons why a function has to access some shared memory, e.g. malloc
and free
.