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.
Comments on this entry are closed.
Is that the one and three are same?
And an example, for the fourth one, may be the important/needed one, just my opinion
Hi,
Thanks a lot….
The first thread safe code will segfault, testing var index is critical here.
you mention non-recursive mutex. Might be useful to explain difference between recursive/non-recursive mutex. In general not a good idea to mix too many concepts in the same example. Great work as usual otherwise.
You say
“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.”
Can you please explain why?
The first chunk of code has an off by one overflow error (you are killing the fairies). You should lock the mutex before the index check or else you will overflow your buffer.
Good work. I like your article.
You can also read something about mutex here.
———————————————————–
int tmp;
tmp = *ptr1;
*ptr1 = *ptr2;
*ptr2 = *tmp;
—————————————————————
is this right ?
“int tmp;
tmp = *ptr1;
*ptr1 = *ptr2;
*ptr2 = *tmp;”
Should read:
int tmp;
tmp = *ptr1;
*ptr1 = *ptr2;
*ptr2 = tmp;
tmp is an int, not a pointer to an int.
Hi! I am glad to read your article. but I have some questions about the Reentrant Function. As your “4. Re-entrant but not Thread Safe” said “So if a function is re-entrant then it does not guarantee that its thread safe.” . But As I know a re-entrant function must be thread safe, can you give me some code explain “4. Re-entrant but not Thread Safe” ? Thanks
Good description. But the first threadsafe example (after making it thread safe) has a few problems. One of them has already been pointed out by Kevin. But there is also a problem with the last statement.
Essentially, the way the code is written, the function cannot be made thread safe (literally any part of it). It is not just the updates that have to be in a critical region. As long as there is a read and write to a variable within the function the entire region (from the first ref to the last ref of the variable) will have to be made a critical region.
But it can be modified slightly to make it threadsafe. I believe the corrected code below makes it threadsafe.
Here is the modified code (not elegant as there are 2 unlocks and one lock 🙁
(not actually compiled; but you get the idea):
char arr[10];
int index=0;
int func(char c)
{
int i=0, tmp;
/* Lock a mutex here */
tmp = index;
if(index >= sizeof(arr))
{
printf(“\n No storage\n”);
/* unlock the mutex here */
return -1;
}
index++;
/* unlock the mutex here */
arr[tmp] = c;
return tmp;
}
Hello,
Are you sure that function is reentrant?
You’re dealing with a pointer so ptr1 and ptr2 can be accessed and modified by both Threads or Processes.
I found a case that you call this function 2 times and didn’t have the desired result.
very good article…example for 4th case would have made it perfect…too much to expect…:)
in function exchange_values
int tmp;
tmp = *ptr1;
*ptr1 = *ptr2;
(interrupt here) non-reentrant
*ptr2 = tmp;