From 0dec51beb694e8b7524fe9fe44f3b390a86f78c0 Mon Sep 17 00:00:00 2001 From: Mathew Sutcliffe Date: Sun, 18 Oct 2015 21:51:50 +0100 Subject: [PATCH] refs #495 LockFree wrapper class. --- src/blackmisc/lockfree.h | 364 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 src/blackmisc/lockfree.h diff --git a/src/blackmisc/lockfree.h b/src/blackmisc/lockfree.h new file mode 100644 index 000000000..225d72b77 --- /dev/null +++ b/src/blackmisc/lockfree.h @@ -0,0 +1,364 @@ +/* Copyright (C) 2015 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKMISC_LOCKFREE_H +#define BLACKMISC_LOCKFREE_H + +#include "blackmisc/variant.h" +#include +#include +#include +#include + +#if _MSC_VER < 1900 +#define BLACK_RVALUE_REF_QUALIFIER +#else +#define BLACK_RVALUE_REF_QUALIFIER && +#endif + +// http://www.drdobbs.com/lock-free-data-structures/184401865 +// http://en.cppreference.com/w/cpp/memory/shared_ptr/atomic + +namespace BlackMisc +{ + + template + class LockFree; + + /*! + * Return value of LockFree::read(). Allows any one thread to safely read from the lock-free object. + */ + template + class LockFreeReader + { + public: + //! Return the value that was present when the reader was created. + //! @{ + const T &get() const { return *m_ptr; } + const T *operator ->() const { return m_ptr.get(); } + const T &operator *() const { return *m_ptr; } + operator const T &() const { return *m_ptr; } + //! @} + + //! Copy constructor. + LockFreeReader(const LockFreeReader &) = default; + + //! Copy assignment operator. + LockFreeReader &operator =(const LockFreeReader &) = default; + + private: + friend class LockFree::type>; + + LockFreeReader(std::shared_ptr ptr) : m_ptr(ptr) {} + std::shared_ptr m_ptr; + }; + + /*! + * Return value of LockFree::uniqueWrite(). Allows any one thread to safely write to the lock-free object, + * as long as no other thread write to it. + */ + template + class LockFreeUniqueWriter + { + public: + //! The value can be modified through the returned reference. The modification is applied in the destructor. + //! @{ + T &get() { return *m_ptr; } + T *operator ->() { return m_ptr.get(); } + T &operator *() { return *m_ptr; } + operator T &() { return *m_ptr; } + //! @} + + //! Replace the stored value by copying from a T. The change is applied in the destructor. + LockFreeUniqueWriter &operator =(const T &other) { *m_ptr = other; return *this; } + + //! Replace the stored value by moving from a T. The change is applied in the destructor. + LockFreeUniqueWriter &operator =(T &&other) { *m_ptr = std::move(other); return *this; } + + //! LockFreeUniqueWriter cannot be copied. + //! @{ + LockFreeUniqueWriter(const LockFreeUniqueWriter &) = delete; + LockFreeUniqueWriter &operator =(const LockFreeUniqueWriter &) = delete; + //! @} + + //! Move constructor. + LockFreeUniqueWriter(LockFreeUniqueWriter &&other) : m_old(std::move(other.m_old)), m_now(std::move(other.m_now)), m_ptr(std::move(other.m_ptr)) {} + + //! Move assignment operator. + LockFreeUniqueWriter &operator =(LockFreeUniqueWriter &&other) + { + std::tie(m_old, m_now, m_ptr) = std::forward_as_tuple(std::move(other.m_old), std::move(other.m_now), std::move(other.m_ptr)); + return *this; + } + + //! Destructor. The original object will be overwritten by the new one stored in the writer. + ~LockFreeUniqueWriter() Q_DECL_NOEXCEPT + { + if (m_ptr.use_count() == 0) { return; } // *this has been moved from + bool success = std::atomic_compare_exchange_strong(m_now, &m_old, std::shared_ptr(m_ptr)); + Q_ASSERT_X(success, qPrintable(name()), "UniqueWriter detected simultaneous writes"); + Q_UNUSED(success); + } + + private: + friend class LockFree; + + LockFreeUniqueWriter(std::shared_ptr ptr, std::shared_ptr *now) : m_old(ptr), m_now(now), m_ptr(std::make_shared(*m_old)) {} + std::shared_ptr m_old; + std::shared_ptr *m_now; + std::shared_ptr m_ptr; + + static QString name() { return QString("LockFree<") + QMetaType::typeName(Private::MetaTypeHelper::maybeGetMetaTypeId()) + ">"; } + }; + + /*! + * Return value of LockFree::sharedWrite(). Allows any one thread to safely write to the lock-free object. + */ + template + class LockFreeSharedWriter + { + public: + //! The value can be modified through the returned reference. The modification is applied by evaluating in a bool context. + //! @{ + T &get() { return *m_ptr; } + T *operator ->() { return m_ptr.get(); } + T &operator *() { return *m_ptr; } + operator T &() { return *m_ptr; } + //! @} + + //! Replace the stored value by copying from a T. The change is applied by evaluating in a bool context. + LockFreeSharedWriter &operator =(const T &other) { *m_ptr = other; return *this; } + + //! Replace the stored value by moving from a T. The change is applied by evaluating in a bool context. + LockFreeSharedWriter &operator =(T &&other) { *m_ptr = std::move(other); return *this; } + + //! Try to overwrite the original object with the new one stored in the writer, and return false on success. + //! If true is returned, then the caller must try again. This would happen if another simultaneous write had occurred. + bool operator !() { return ! operator bool(); } + + //! Try to overwrite the original object with the new one stored in the writer, and return true on success. + //! If false is returned, then the caller must try again. This would happen if another simultaneous write had occurred. + operator bool() + { + Q_ASSERT_X(m_ptr.use_count() > 0, qPrintable(name()), "SharedWriter tried to commit changes twice"); + if (std::atomic_compare_exchange_strong(m_now, &m_old, std::shared_ptr(m_ptr))) + { + m_ptr.reset(); + return true; + } + QThread::msleep(1); + m_old = std::atomic_load(m_now); + m_ptr = std::make_shared(*m_old); + return false; + } + + //! Destructor. The writer's changes must be committed before this is called. + ~LockFreeSharedWriter() Q_DECL_NOEXCEPT + { + Q_ASSERT_X(m_ptr.use_count() == 0, qPrintable(name()), "SharedWriter destroyed without committing changes"); + } + + //! LockFreeSharedWriter cannot be copied. + //! @{ + LockFreeSharedWriter(const LockFreeSharedWriter &) = delete; + LockFreeSharedWriter &operator =(const LockFreeSharedWriter &) = delete; + //! @} + + //! Move constructor. + LockFreeSharedWriter(LockFreeSharedWriter &&other) : m_old(std::move(other.m_old)), m_now(std::move(other.m_now)), m_ptr(std::move(other.m_ptr)) {} + + //! Move assignment operator. + LockFreeSharedWriter &operator =(LockFreeSharedWriter &&other) + { + std::tie(m_old, m_now, m_ptr) = std::forward_as_tuple(std::move(other.m_old), std::move(other.m_now), std::move(other.m_ptr)); + return *this; + } + + private: + friend class LockFree; + + LockFreeSharedWriter(std::shared_ptr ptr, std::shared_ptr *now) : m_old(ptr), m_now(now), m_ptr(std::make_shared(*m_old)) {} + std::shared_ptr m_old; + std::shared_ptr *m_now; + std::shared_ptr m_ptr; + + static QString name() { return QString("LockFree<") + QMetaType::typeName(Private::MetaTypeHelper::maybeGetMetaTypeId()) + ">"; } + }; + + /*! + * Lock-free wrapper for synchronizing multi-threaded access to an object. + * + * Implemented using atomic operations of std::shared_ptr. + */ + template + class LockFree + { + public: + //! Default constructor. Object will contain a default-constructed T. + LockFree() = default; + + //! Construct by copying from a T. + LockFree(const T &other) : m_ptr(std::make_shared(other)) {} + + //! Construct by moving from a T. + LockFree(T &&other) : m_ptr(std::make_shared(std::move(other))) {} + + //! LockFree cannot be copied or moved. + //! @{ + LockFree(const LockFree &) = delete; + LockFree &operator =(const LockFree &) = delete; + LockFree(LockFree &&) = delete; + LockFree &operator =(LockFree &&) = delete; + //! @} + + //! Return an object which can read the current value. + LockFreeReader read() const + { + return { std::atomic_load(&m_ptr) }; + } + + //! Return an object which can write a new value, as long as there are no other writes. + LockFreeUniqueWriter uniqueWrite() + { + return { std::atomic_load(&m_ptr), &m_ptr }; + }; + + //! Return an object which can write a new value, even if there are other writes. + LockFreeSharedWriter sharedWrite() + { + return { std::atomic_load(&m_ptr), &m_ptr }; + }; + + //! Pass the current value to the functor inspector, and return whatever inspector returns. + template + auto read(F &&inspector) -> decltype(inspector(std::declval())) + { + return std::forward(inspector)(read().get()); + } + + //! Pass a modifiable reference to the functor mutator. Unsafe if there are multiple writers. + template + void uniqueWrite(F &&mutator) + { + std::forward(mutator)(uniqueWrite().get()); + } + + //! Pass a modifiable reference to the functor mutator. Safe if there are multiple writers. + //! The mutator may be called multiple times. + template + void sharedWrite(F &&mutator) + { + auto writer = sharedWrite(); + do { std::forward(mutator)(writer.get()); } while (! writer); + } + + private: + std::shared_ptr m_ptr = std::make_shared(); + }; + + /*! + * Compose multiple LockFreeReader or LockFreeUniqueWriter instances. + */ + template