This is a very simple read-copy-update (RCU)-inspired synchronization class:
#include <atomic>
template<typename T>
class RCU
{
private:
std::atomic<T*> curr;
std::atomic<T*> prev;
std::atomic<int> readers;
public:
RCU()
: curr (new T),
prev (nullptr),
readers(0)
{
}
~RCU()
{
delete curr;
delete prev;
}
void lock()
{
readers++;
}
void unlock()
{
readers--;
}
T* get()
{
return curr.load();
}
T* clone()
{
return new T(*curr.load());
}
bool swap(T* t)
{
/* If prev is full, a swap is still pending. Clean up memory and
abort the operation. */
if (prev.load() != nullptr)
{
delete t;
return false;
}
/* Store the current pointer into prev. Readers might still be reading
through the pointer. */
prev.store(curr);
/* Curr contains the new proposed value. No one is reading from this
right now. */
curr.store(t);
/* Spin until there are no more readers to *prev. When so, free the
memory it points to and say goodbye. */
while (readers.load() > 0);
T* old = prev.load();
prev.store(nullptr);
delete old;
return true;
}
};
The idea is to have many reader threads that repeatedly read from some shared data, while a writer thread that rarely updates it. The writer thread takes care of cleaning up the memory when no readers are reading. Readers are "tracked" through the lock() and unlock() methods. The RCU-like class should synchronize the threads in a lock-free way.
Usage:
struct Data
{
int x;
int y;
int z;
};
RCU<Data> rcu;
void writer_thread() // the writer rarely runs
{
while (true)
{
Data* data = rcu.clone();
// modify data as needed
data->x++;
data->y++;
data->z++;
rcu.swap(data); // Checking the return value might be useful
}
}
void reader_thread()
{
while (true)
{
rcu.lock();
std::cout << rcu.get()->x << "\n"; // read from data
rcu.unlock();
}
}
Questions: does it make sense? Is it really thread-safe? Can a thread slip through the cracks somewhere, somehow?