I am trying to implement the following functionality:
- Atomic and lock-free write or read-modify-write of a data type with arbitrary size (in my case usually a float/int vector with up to 6 elements).
- Atomic read from above data type that doesn't block the writing thread. The read operation may be blocked by the write operation.
Usecase: I am trying to write the software for a CNC machine. The step pulses for the motors are generated in software in a realtime thread. This realtime thread is constantly updating a variable which holds the current position of the axis. Multiple other non-realtime threads may read that variable, e.g. to display the current position.
Question 1: Is there a standard/accepted solution or pattern for this kind of problem?
I came up with the following idea: use an std::atomic<uint64_t>
to protect the data and track weather a thread is currently writing (by checking the last bit) or has written since the read started (by incrementing the value on a write).
template <class DATA, class FN>
void read_modify_write(DATA& data, std::atomic<uint64_t>& protector, FN fn)
{
auto old_protector_value = protector.load();
do
{
// wait until no other thread is writing
while(old_protector_value % 2 != 0)
old_protector_value = protector.load();
// try to acquire write privileges
} while(!protector.compare_exchange_weak(old_protector_value, old_protector_value + 1));
// write data
data = fn(data);
// unlock
protector = old_protector_value + 2;
};
template <class DATA>
DATA read(const DATA& data, std::atomic<uint64_t>& protector)
{
while(true)
{
uint64_t old_protector_value = protector.load();
// wait until no thread is writing
while(old_protector_value % 2 != 0)
old_protector_value = protector.load();
// read data
auto ret = data;
// check if data has changed in the meantime
if(old_protector_value == protector)
return ret;
}
}
Question 2: Is the above code thread-safe and fulfilling above requirements?
Question 3: Can it be improved?
(The only theoretical problem I could find is if the counter wraps around, i.e. exactly 2^63 write operations are performed during 1 read operation. I would consider this weakness acceptable if there are no better solutions.)
Thank you