Wrap any object with Synchronized<> and it can be used similarly to the base type, but safely in a thread safe manner, from multiple threads. This is similar to std::atomic. More...
#include <Synchronized.h>
Inherits std::conditional_t< TRAITS::kDbgTraceLockUnlockIfNameSet, Private_::DbgTraceNameObj_, Common::Empty >.
Classes | |
class | ReadableReference |
Public Member Functions | |
template<typename... ARGUMENT_TYPES> | |
Synchronized (ARGUMENT_TYPES &&... args) | |
nonvirtual | operator T () const |
nonvirtual T | load () const |
nonvirtual void | store (const T &v) |
nonvirtual ReadableReference | cget () const |
get a read-only smart pointer to the underlying Synchronized<> object, holding the readlock the whole time the return (often temporary) ReadableReference exists. | |
nonvirtual WritableReference | rwget () |
get a read-write smart pointer to the underlying Synchronized<> object, holding the full lock the whole time the (often temporary) WritableReference exists. | |
nonvirtual void | lock_shared () const |
nonvirtual void | unlock_shared () const |
nonvirtual void | lock () const |
nonvirtual bool | try_lock () const |
nonvirtual bool | try_lock_for (const chrono::duration< double > &tryFor) const |
nonvirtual void | unlock () const |
nonvirtual bool | UpgradeLockNonAtomicallyQuietly (ReadableReference *lockBeingUpgraded, const function< void(WritableReference &&)> &doWithWriteLock, Time::DurationSeconds timeout=Time::kInfinity) |
Upgrade a shared_lock (ReadableReference) to a (WritableReference) full lock temporarily in the context of argument function; return true if succeeds, and false if fails (timeout trying to full-lock) or argument doWithWriteLock () returns false. | |
nonvirtual void | UpgradeLockNonAtomically (ReadableReference *lockBeingUpgraded, const function< void(WritableReference &&)> &doWithWriteLock, Time::DurationSeconds timeout=Time::kInfinity) |
Same as UpgradeLockNonAtomicallyQuietly, but throws on failure (either timeout or if argument function returns false) | |
Wrap any object with Synchronized<> and it can be used similarly to the base type, but safely in a thread safe manner, from multiple threads. This is similar to std::atomic.
The idea behind any of these synchronized classes is that they can be used freely from different threads without worry of data corruption. It is almost as if each operation were preceeded with a mutex lock (on that object) and followed by an unlock.
If one thread does a Read operation on Synchronized<T> while another does a write (modification) operation on Synchronized<T>, the Read will always return a consistent reasonable value, from before the modification or afterwards, but never a distorted invalid value.
This is very much closely logically the java 'synchronized' attribute, except that its not a language extension/attribute here, but rather a class wrapper. Its also implemented in the library, not the programming language.
Because with Java - you mix up exceptions and assertions. With Stroika, we have builtin checking for races (Debug::AssertExternallySynchronizedMutex) in most objects, so you only use Synchronized<> (or some other more performant mechanism) in the few places you need it.
or slightly faster, but possibly slower or less safe (depending on usage)
or to allow timed locks
or to read-modify-update in place
or to read-modify-update with explicit temporary
or, because Synchronized<> has lock/unlock methods, it can be used with a lock_guard (if associated mutex recursive), as in:
So ONLY support operator-> const overload (brevity and more common than for write). To write - use rwget().
Instead, just have upgradeLock release the shared_lock, and re-acquire the mutex as a full lock. BUT - this has problems too. Typically - you compute something with the shared_lock and notice you want to commit a change, and so upgrade to get the full lock. But when you do the upgrade, someone else could sneak in and do the same thing invalidating your earlier computation.
So - the Upgrade lock APIS have the word "NON_ATOMICALLY" in the name to emphasize this issue, and either return a boolean indicating failure, or take a callback that gets notified of the need to recompute the cached value/data.
Definition at line 241 of file Synchronized.h.
Stroika::Foundation::Execution::Synchronized< T, TRAITS >::Synchronized | ( | ARGUMENT_TYPES &&... | args | ) |
Create a Synchronized with any argument type the underlying type will be constructed with the same (perfectly) forwarded arguments.
And plain copy constructor.
FOR NOW, avoid MOVE constructor (cuz synchronized is a combination of original data with lock, and not sure what moving the lock means).
Definition at line 30 of file Synchronized.inl.
Stroika::Foundation::Execution::Synchronized< T, TRAITS >::operator T | ( | ) | const |
Synonym for load ()
Definition at line 73 of file Synchronized.inl.
T Stroika::Foundation::Execution::Synchronized< T, TRAITS >::load | ( | ) | const |
Note - unlike operator->, load () returns a copy of the internal data, and only locks while fetching it, so that the lock does not persist while using the result.
Definition at line 79 of file Synchronized.inl.
void Stroika::Foundation::Execution::Synchronized< T, TRAITS >::store | ( | const T & | v | ) |
Save the given value into this synchronized object, acquiring the needed write lock first.
Definition at line 96 of file Synchronized.inl.
auto Stroika::Foundation::Execution::Synchronized< T, TRAITS >::cget | ( | ) | const |
get a read-only smart pointer to the underlying Synchronized<> object, holding the readlock the whole time the return (often temporary) ReadableReference exists.
This is roughly equivalent (if using a recursive mutex) to (COUNTER_EXAMPLE):
NOTE - THIS EXAMPLE USAGE IS UNSAFE - DONT DO!
Except that this works whether using a shared_mutex or regular mutex. Also - this provides only read-only access (use rwget for read-write access).
Definition at line 142 of file Synchronized.inl.
auto Stroika::Foundation::Execution::Synchronized< T, TRAITS >::rwget | ( | ) |
get a read-write smart pointer to the underlying Synchronized<> object, holding the full lock the whole time the (often temporary) WritableReference exists.
Definition at line 157 of file Synchronized.inl.
void Stroika::Foundation::Execution::Synchronized< T, TRAITS >::lock_shared | ( | ) | const |
Definition at line 177 of file Synchronized.inl.
void Stroika::Foundation::Execution::Synchronized< T, TRAITS >::unlock_shared | ( | ) | const |
Definition at line 187 of file Synchronized.inl.
void Stroika::Foundation::Execution::Synchronized< T, TRAITS >::lock | ( | ) | const |
Definition at line 197 of file Synchronized.inl.
bool Stroika::Foundation::Execution::Synchronized< T, TRAITS >::try_lock | ( | ) | const |
Definition at line 208 of file Synchronized.inl.
bool Stroika::Foundation::Execution::Synchronized< T, TRAITS >::try_lock_for | ( | const chrono::duration< double > & | tryFor | ) | const |
Definition at line 222 of file Synchronized.inl.
void Stroika::Foundation::Execution::Synchronized< T, TRAITS >::unlock | ( | ) | const |
Definition at line 236 of file Synchronized.inl.
bool Stroika::Foundation::Execution::Synchronized< T, TRAITS >::UpgradeLockNonAtomicallyQuietly | ( | ReadableReference * | lockBeingUpgraded, |
const function< void(WritableReference &&)> & | doWithWriteLock, | ||
Time::DurationSeconds | timeout = Time::kInfinity |
||
) |
Upgrade a shared_lock (ReadableReference) to a (WritableReference) full lock temporarily in the context of argument function; return true if succeeds, and false if fails (timeout trying to full-lock) or argument doWithWriteLock () returns false.
A DEFEFCT with (this) UpgradeLockNonAtomically API, is that you cannot count on values computed with the read lock to remain valid in the upgrade lock (since we unlock and then re-lock). We resolve this by having two versions of UpgradeLockNonAtomically, one where the callback gets notified there was an intervening writelock, and one where the entire call fails and you have to re-run.
Definition at line 270 of file Synchronized.inl.
void Stroika::Foundation::Execution::Synchronized< T, TRAITS >::UpgradeLockNonAtomically | ( | ReadableReference * | lockBeingUpgraded, |
const function< void(WritableReference &&)> & | doWithWriteLock, | ||
Time::DurationSeconds | timeout = Time::kInfinity |
||
) |
Same as UpgradeLockNonAtomicallyQuietly, but throws on failure (either timeout or if argument function returns false)
Definition at line 327 of file Synchronized.inl.