Writer-perferring read-write lock.
This class is based on an implementation of a "write-preferring RW lock" as described in this wikipedia page:
https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Using_a_condition_variable_and_a_mutex
Copying here for posterity:
* For a write-preferring RW lock one can use two integer counters and one boolean flag:
*
* num_readers_active: the number of readers that have acquired the lock (integer)
* num_writers_waiting: the number of writers waiting for access (integer)
* writer_active: whether a writer has acquired the lock (boolean).
*
* Initially num_readers_active and num_writers_waiting are zero and writer_active is false.
*
* The lock and release operations can be implemented as
*
* Begin Read
*
* Lock g
* While num_writers_waiting > 0 or writer_active:
* wait cond, g[a]
* Increment num_readers_active
* Unlock g.
*
* End Read
*
* Lock g
* Decrement num_readers_active
* If num_readers_active = 0:
* Notify cond (broadcast)
* Unlock g.
*
* Begin Write
*
* Lock g
* Increment num_writers_waiting
* While num_readers_active > 0 or writer_active is true:
* wait cond, g
* Decrement num_writers_waiting
* Set writer_active to true
* Unlock g.
*
* End Write
*
* Lock g
* Set writer_active to false
* Notify cond (broadcast)
* Unlock g.
*
It will prefer any waiting write calls to any waiting read calls, meaning that excessive write calls can starve read calls.
This class diverges from that design in two important ways. First, it is a single reader, single writer version. Second, it allows for user defined code to be run after a writer enters the waiting state, and the purpose of this feature is to allow the user to interrupt any potentially long blocking read activities.
Together these two features allow new waiting writers to not only ensure they get the lock before any queued readers, but also that it can safely interrupt read activities if needed, without allowing new read activities to start before it gains the lock.
The first difference prevents the case that a multiple read activities occur at the same time but the writer can only reliably interrupt one of them. By preventing multiple read activities concurrently, this case is avoided. The second difference allows the user to define how to interrupt read activity that could be blocking the write activities that need to happen as soon as possible.
To implement the differences, this class replaces the "num_readers_active" counter with a "reader_active" boolean. It also changes the "Begin Read" section from above, like this:
* Begin Read
*
* Lock g
* While num_writers_waiting > 0 or writer_active or reader_active: // changed
* wait cond, g[a]
* Set reader_active to true // changed
* Unlock g.
*
And changes the "End Read" section from above, like this:
* End Read
*
* Lock g
* Set reader_active to false // changed
* Notify cond (broadcast) // changed, now unconditional
* Unlock g.
*
The "Begin Write" section is also updated as follows:
* Begin Write
*
* Lock g
* Increment num_writers_waiting
* Call user defined enter_waiting function // new
* While reader_active is true or writer_active is true: // changed
* wait cond, g
* Decrement num_writers_waiting
* Set writer_active to true
* Unlock g.
*
The implementation uses a single condition variable, single lock, and several state variables.
The typical use of this class is as follows:
class MyClass
{
WritePreferringReadWriteLock wprw_lock_;
public:
MyClass() {}
void do_some_reading()
{
using rclcpp::wait_set_policies::detail::WritePreferringReadWriteLock;
std::lock_guard<WritePreferringReadWriteLock::ReadMutex> lock(wprw_lock_.get_read_mutex());
// Do reading...
}
void do_some_writing()
{
using rclcpp::wait_set_policies::detail::WritePreferringReadWriteLock;
std::lock_guard<WritePreferringReadWriteLock::WriteMutex> lock(wprw_lock_.get_write_mutex());
// Do writing...
}
};