0

How to synchronize data access

 3 years ago
source link: https://vorbrodt.blog/2021/08/17/how-to-synchronize-data-access/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

How to synchronize data access

I came across a post titled C++ Locking Wrapper shared by Meeting C++ on Twitter and it reminded me of Synchronized Data Structures and boost::synchronized_value so I decided to implement my own version as a learning exercise and possible topic of a future video on my YT channel.

The idea is simple: synchronize across multiple threads every method call to an instance of an object; do it in the most transparent and un-obstructing way possible. Here’s a simple example illustrating the idea:

Usage example
#include <thread>
#include <vector>
#include <cstdlib>
#include "synchronized.hpp"
int main()
using namespace std;
srand(time(NULL));
synchronized<vector<int>> sv;
//auto sv = new vector<int>;
thread t1([&] ()
while(true)
sv->push_back(rand());
thread t2([&] ()
while(true)
sv->clear();
t1.join();
t2.join();

The trick comes down to two things really: 1) Wrap a value of type T and its lock (in my case std::mutex) in a containing class and 2) Override operator->() to return a RAII temporary responsible for guarding the value.

I will add that this is perhaps a heavy handed approach to guarding data since I do not know how to make it transparent while allowing reader/writer locks, or synchronization of only some select methods. Perhaps some type system hackery with const and volatile methods could help here…


Implementation:
synchronized.hpp | synchronized.cpp


Implementation
#include <mutex>
#include <utility>
#include <type_traits>
namespace detail
template<typename U>
class locker
public:
locker(U& v, std::recursive_mutex& l) noexcept
: m_value(v), m_lock(l) { m_lock.lock(); }
locker(const locker&) = delete;
locker& operator = (const locker&) = delete;
~locker() noexcept { m_lock.unlock(); }
U* operator -> () noexcept { return &m_value; }
private:
U& m_value;
std::recursive_mutex& m_lock;
template<typename T>
class synchronized
public:
synchronized() = default;
synchronized(const T& v) : m_value(v) {}
synchronized(T&& v) : m_value(std::move(v)) {}
template<typename... A, std::enable_if_t<
std::is_constructible_v<T, A...>>* = nullptr>
synchronized(A&&... a) : m_value(std::forward<A>(a)...) {}
template<typename V, std::enable_if_t<
std::is_constructible_v<T, std::initializer_list<V>>>* = nullptr>
synchronized(std::initializer_list<V> l) : m_value(l) {}
synchronized(const synchronized& other) : m_value(other.m_value) {}
synchronized(synchronized&& other) : m_value(std::move(other.m_value)) {}
synchronized& operator = (synchronized other) noexcept
std::swap(m_value, other.m_value);
return *this;
detail::locker<T> operator -> () noexcept
return detail::locker(m_value, m_lock);
private:
T m_value;
mutable std::recursive_mutex m_lock;

Like this:

Loading...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK