I'm experimenting with the C++11 atomic primitives to implement an atomic "thread counter" of sorts. Basically, I have a single critical section of code. Within this code block, any thread is free to READ from memory. However, sometimes, I want to do a reset or clear operation, which resets all shared memory to a default initialized value.
This seems like a great opportunity to use a read-write lock. C++11 doesn't include read-write mutexes out of the box, but maybe something simpler will do. I thought this problem would be a great opportunity to become more familiar with C++11 atomic primitives.
So I thought through this problem for a while, and it seems to me that all I have to do is :
Whenever a thread enters the critical section, increment an atomic counter variable
Whenever a thread leaves the critical section, decrement the atomic counter variable
If a thread wishes to reset all variables to default values, it must atomically wait for the counter to be 0, then atomically set it to some special "clearing flag" value, perform the clear, then reset the counter to 0.
Of course, threads wishing to increment and decrement the counter must also check for the clearing flag.
So, the algorithm I just described can be implemented with three functions. The first function, increment_thread_counter()
must ALWAYS be called before entering the critical section. The second function, decrement_thread_counter()
, must ALWAYS be called right before leaving the critical section. Finally, the function clear()
can be called from outside the critical section only iff the thread counter == 0.
This is what I came up with:
Given:
- A thread counter variable,
std::atomic<std::size_t> thread_counter
- A constant
clearing_flag
set tostd::numeric_limits<std::size_t>::max()
...
void increment_thread_counter()
{
std::size_t expected = 0;
while (!std::atomic_compare_exchange_strong(&thread_counter, &expected, 1))
{
if (expected != clearing_flag)
{
thread_counter.fetch_add(1);
break;
}
expected = 0;
}
}
void decrement_thread_counter()
{
thread_counter.fetch_sub(1);
}
void clear()
{
std::size_t expected = 0;
while (!thread_counter.compare_exchange_strong(expected, clearing_flag)) expected = 0;
/* PERFORM WRITES WHICH WRITE TO ALL SHARED VARIABLES */
thread_counter.store(0);
}
As far as I can reason, this should be thread-safe. Note that the decrement_thread_counter
function shouldn't require ANY synchronization logic, because it is a given that increment()
is always called before decrement()
. So, when we get to decrement()
, thread_counter can never equal 0 or clearing_flag
.
Regardless, since THREADING IS HARD™, and I'm not an expert at lockless algorithms, I'm not entirely sure this algorithm is race-condition free.
Question: Is this code thread safe? Are any race conditions possible here?