/* 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/blackmiscexport.h" #include "blackmisc/integersequence.h" #include "blackmisc/variant.h" #include "blackmisc/variantprivate.h" #include #include #include #include #include #include #include #include #include #include #include // http://www.drdobbs.com/lock-free-data-structures/184401865 // http://en.cppreference.com/w/cpp/memory/shared_ptr/atomic namespace BlackMisc { template class LockFree; namespace Private { //! \cond PRIVATE //! \private BLACKMISC_EXPORT QMutex *atomicSharedPtrMutex(); //! \endcond } /*! * 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>; 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) noexcept(std::is_nothrow_move_assignable::value) { *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) noexcept : 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) noexcept { 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() { 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) noexcept(std::is_nothrow_move_assignable::value) { *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_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) noexcept : 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) noexcept { 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) noexcept(std::is_nothrow_move_assignable::value) : 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) { 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