/* SYNC.hpp - generic helper for object based locking and synchronisation Copyright (C) Lumiera.org 2008, Christian Thaeter Hermann Vosseler This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** @file sync.hpp ** Object Monitor based synchronisation. ** The actual locking, signalling and waiting is implemented by delegating to the ** a _mutex_ and for waiting also to a _condition variable_ as provided by the C++ ** standard library. The purpose of the Sync baseclass is to provide a clear and simple ** „everyday“ concurrency coordination based on the *Object Monitor Pattern*. This ** pattern describes a way of dealing with synchronisation known to play well with ** scoping, encapsulation and responsibility for a single purpose. For performance ** critical code, other solutions (e.g. Atomics) might be preferable. ** ** # Usage ** ** A class becomes _lockable_ by inheriting from lib::Sync with the appropriate ** parametrisation. This causes any instance to inherit a monitor member (object), ** maintaining a dedicated a mutex and (optionally) a condition variable for waiting. ** The actual synchronisation is achieved by placing a guard object as local (stack) ** variable into a given scope (typically a member function body). This guard object ** of class lib::Sync::Lock accesses the enclosing object's monitor and automatically ** manages the locking and unlocking; optionally it may also be used to perform a ** [wait-on-condition] — the call to `Lock::wait(predicate)` will first check the ** predicate, and if it does not yield `true`, the thread will be put to sleep. ** It must be awakened from another thread by invoking `notify_one|all` and will ** then re-check the condition predicate. The `wait_for` variant allows to set ** a timeout to limit the sleep state, which implies however that the call may ** possibly return `false` in case the condition predicate is not (yet) fulfilled. ** @note ** - It is important to select a suitable parametrisation of the monitor. ** This is done by specifying one of the defined policy classes. ** - Be sure to pick the recursive mutex implementation when recursive calls ** of synchronised functions can't be avoided. (performance penalty) ** - You can't use the Lock#wait and Lock#notify functions unless you pick ** a parametrisation including a condition variable. ** - The "this" pointer is fed to the ctor of the Lock guard object. Thus ** you may use any object's monitor as appropriate, especially in cases ** when adding the monitor to a given class may cause size problems. ** - There is a special variant of the Lock guard called ClassLock, which ** can be used to lock based on a type, not an instance. ** ** [wait-on-condition]: https://en.cppreference.com/w/cpp/thread/condition_variable_any/wait ** @see mutex.h ** @see sync-locking-test.cpp ** @see sync-waiting-test.cpp ** @see \ref steam::control::DispatcherLoop "usage example: receiving session coomands" ** @see \ref subsystem-runner.hpp "usage example: subsystem start/shutdown" */ #ifndef LIB_SYNC_H #define LIB_SYNC_H #include "lib/error.hpp" #include "lib/nocopy.hpp" #include "lib/util.hpp" #include #include #include namespace lib { /** Helpers and building blocks for Monitor based synchronisation */ namespace sync { /* ========== policies to define required degree of locking functionality ============== */ template struct Condition : MTX , std::condition_variable_any { template void wait (PRED&& predicate) { condVar().wait (mutex(), std::forward (predicate)); } /** @return `false` in case of timeout, * `true` if predicate is fulfilled at return */ template bool wait_for (std::chrono::duration const& timeout, PRED&& predicate) { return condVar().wait_for (mutex(), timeout, std::forward (predicate)); } MTX& mutex() { return static_cast (*this); } std::condition_variable_any& condVar() { return static_cast (*this); } }; struct NoLocking { void lock() { /* boo */ } void unlock() noexcept { /* hoo */ } }; /* ========== Object Monitor ============== */ /** * Object Monitor for synchronisation and waiting. * Implemented by a (wrapped) set of sync primitives, * which are default constructible and noncopyable. */ template class Monitor : IMPL , util::NonCopyable { public: void lock() { IMPL::lock(); } void unlock() noexcept { IMPL::unlock(); } void notify_one() noexcept { IMPL::notify_one(); } void notify_all() noexcept { IMPL::notify_all(); } template void wait (PRED&& predicate) { IMPL::wait (std::forward(predicate)); } template bool wait_for (DUR const& timeout, PRED&& predicate) { return IMPL::wait_for (timeout, std::forward (predicate)); } }; using NonrecursiveLock_NoWait = std::mutex; using RecursiveLock_NoWait = std::recursive_mutex; using NonrecursiveLock_Waitable = Condition; using RecursiveLock_Waitable = Condition; } // namespace sync (helpers and building blocks) /* Interface to be used by client code: * Inherit from class Sync with a suitable Policy. * Then use the embedded Lock class. */ /* ======= Policy classes ======= */ using sync::NonrecursiveLock_NoWait; using sync::NonrecursiveLock_Waitable; using sync::RecursiveLock_NoWait; using sync::RecursiveLock_Waitable; /*********************************************************************//** * Facility for monitor object based locking. * To be attached either on a per class base or per object base. * Typically, the client class will inherit from this template (but it * is possible to use it stand-alone, if inheriting isn't an option). * The interface for clients to access the functionality is the embedded * Lock template, which should be instantiated as an automatic variable * within the scope to be protected. * * # Usage * - for *locking*, just place an instant of the embedded Lock into the local * scope to be protected. All lock instances within the same object share * the monitor; thus any time, only one of them gets the mutex all other * instances block on construction. * - for *waiting* likewise place an instance of the Lock class (which gets * you a mutex). Then invoke the wait method on that instance; this suspends * the current thread and releases the mutex. To awake and check the condition, * some other thread must invoke the Lock::notify() within the same object. * The Lock::wait() call returns `true` when the condition was met, and * `false` if awakened due to timeout. The call might throw in case of * technical errors. In any case, when returning from the `wait()` call, * the mutex has been re-acquired. */ template class Sync { using Monitor = sync::Monitor; mutable Monitor objectMonitor_; public: static Monitor& getMonitor(Sync const* forThis) { REQUIRE (forThis); return forThis->objectMonitor_; } /*****************************************//** * scoped guard to control the actual locking. */ class Lock : util::NonCopyable { Monitor& mon_; public: template Lock(X* it) : mon_{getMonitor(it)}{ mon_.lock(); } ~Lock() { mon_.unlock(); } void notify_one() { mon_.notify_one(); } void notify_all() { mon_.notify_all(); } template void wait (PRED&& predicate) { mon_.wait (std::forward(predicate)); } template bool wait_for (DUR const& timeout, PRED&& predicate) { return mon_.wait_for (timeout, std::forward (predicate)); } /** convenience shortcut: * Locks and immediately enters wait state on the given predicate */ template Lock(X* it, PRED&& predicate) : mon_(getMonitor(it)) { mon_.lock(); try { mon_.wait (std::forward(predicate)); } catch(...) { mon_.unlock(); throw; } } protected: /** for creating a ClassLock */ Lock(Monitor& m) : mon_{m} { mon_.lock(); } /** subclass access to underlying sync primitives */ Monitor& accessMonitor() { return mon_; } }; }; } // namespace lumiera #endif