Reputation: 296
I saw a blog stating the below code is thread safe , but the condition count
not being inside the mutex
would cause a data corruption; in case two threads check the count
at the same time but before either acquires a mutex
and one acquire the mutex
after contending. When one thread completes , the other would very blindly add one more value to the array.
char arr[10];
int count=0;
int func(char ctr)
{
int i=0;
if(count >= sizeof(arr))
{
printf("\n No storage\n");
return -1;
}
/* lock the mutex here*/
arr[count] = ctr;
count++;
/* unlock the mutex here*/
return count;
}
Would I be right if I made the following changes? Or is there a better/efficient way to do it
int func(char ctr)
{
int i=0;
/* lock the mutex here*/
if(count >= sizeof(arr))
{
printf("\n No storage\n");
/* unlock the mutex here*/
return -1;
}
arr[count] = ctr;
count++;
/* unlock the mutex here*/
return count;
}`
Upvotes: 3
Views: 173
Reputation: 10863
Nothing wrong with the previous answers, but there is a better way. A mutex is not needed.
int func(char ctr) {
int c = interlocked_increment(&count);
if (c >= sizeof(arr)) {
printf("\n No storage\n");
interlocked_decrement(&count);
return -1;
}
arr[c-1] = ctr;
return c;
}
This depends on the availability of interlocked increment and decrement functions, which have to be provided by your operating system or third party library.
Every value of c
that is within range is guaranteed to be one not seen by any other thread, and is therefore a valid slot in arr
, and no thread will miss a slot if there is one available. The order of storing the value is indeterminate, but that is true of most of the other solutions too. The maximum value reached by count
if many threads are competing for storage is also indeterminate, and if that is an issue a different approach might be needed. The behaviour if count
is decremented is another unknown. This stuff is hard, and it's always possible to add additional constraints to make it harder.
Just to point out that there is another possible implementation based on a CSET (Check and Set) function. This is a function that checks whether some location is equal to a value and if so sets it to another value atomically, returning true if so. It avoids some of the criticism leveled at the above function.
int func(char ctr) {
for (;;) {
int c = count;
if (c >= sizeof(arr)) {
printf("\n No storage\n");
return -1;
}
if (CSET(&count, c, c+1)) {
arr[c] = ctr;
return c;
}
}
}
The C++ standard atomic operations library contains a set of atomic_compare_exchange functions which should serve the purpose, if available.
Upvotes: 0
Reputation: 34839
First, you are correct that the code from the blog has the potential to write beyond the end of the array. The limit checking only works if it's done after the mutex has been acquired.
Here's how I would write the function:
bool func(char ctr)
{
bool result;
/* lock the mutex here */
if (count >= sizeof(arr))
{
result = FAILURE;
}
else
{
arr[count] = ctr;
count++;
result = SUCCESS;
}
/* unlock the mutex here */
if ( result == FAILURE )
printf("\n No storage\n");
return result;
}
The features of this code worth noting
return
statements in the critical section. This makes it
clear that the mutex will always be unlocked.printf
is outside of the critical section. printf
is
relatively slow, and any function that uses a mutex should hold the
mutex for as little time as possible.bool
indicating success or failure. Any code that needs
to know how many entries are in the array should lock the mutex and
examine the count directly.Upvotes: 2
Reputation: 2397
You are correct. By doing the check outside of the critical section you are opening the doors for a possible buffer overrun. However, note that the returned count may not be the same index used to store ctr. That's an issue even in the corrected code.
In order to remedy that you could rewrite it like this:
int func(char ctr)
{
/* lock the mutex here*/
if(count >= sizeof(arr)) {
printf("\n No storage\n");
/* unlock the mutex here*/
return -1;
}
arr[count] = ctr;
int c = count++;
/* unlock the mutex here*/
return c;
}
It's worth noting that, if that's the only function changing "count", then no two threads would be able to change the same memory position in arr and this would actually be safe:
int func(char ctr)
{
/* lock the mutex here*/
if(count >= sizeof(arr)) {
printf("\n No storage\n");
/* unlock the mutex here*/
return -1;
}
int c = count++;
/* unlock the mutex here*/
arr[c] = ctr;
return c;
}
If that's the pattern, maybe you can refactor that code into two functions, like so:
int get_sequence(void)
{
/* lock the mutex here*/
int c = count++;
/* unlock the mutex here*/
return c;
}
int func(char ctr)
{
int c = get_sequence();
if(c >= sizeof(arr)) {
printf("\n No storage\n");
return -1;
}
arr[c] = ctr;
return c;
}
Note that will only work as long as get_sequence is really the only function changing count variable.
Upvotes: 4