How to "upgrade" mutex ownership from shared to upgrade? - c++

Sorry for my dumb question, but how can I
upgrade shared ownership to upgrade?
So what method is the opposite of the unlock_upgrade_and_lock_shared() one?
(Is there any.)
So if an ownership is shared how can i "upgrade" it to upgrade without unlock_shared() and lock_upgrade()?
And I can not understand the concept of having upgrade ownership.
Why isn't there method like unlock_shared_and_lock()?
So, what are the benefits of having ownership like "upgrade"?
Thanks ahead, and sorry for my English (and silly questions) :) !

Either your thread has upgrade ownership, or it has shared ownership, or it has exclusive ownership. lock_upgrade() converts your ownership from shared to upgrade. Upgrade ownership is provided so you have a direct way to obtain exclusive ownership without releasing the shared lock.
The docs here might prove instructive in understanding the lock ownership types, and how to manage all possible state changes.

Related

How to futureproof thread-safe concurrent access to std::shared_ptr std::unique_ptr

What are some recommended strategies for future-proofing present-day C++ coding of concurrent access to std::shared_ptr(-like) and std::unique_ptr(-like) data structures, as the C++ language spec evolves in this area?
Background:
Circa 2021, available C++ language constructs for managing access to std::shared_ptr(-like) and std::unique_ptr(-like) smart pointers in concurrency-friendly ways are in flux. For example C++20 support for std::atomic<std::shared_ptr> hasn't made it very far into compiler in the wild yet, but the C++20 spec tells us it is coming.
I'm engaged in non-trivial multi-threaded development and need to be able to pass smart pointers (both shared and unique) between threads via (hopefully lock-free) queues and use them in various thread-safe ways. I'd like to develop this code in a way that allows it to be easily upgraded to modern language features once they become available. The ideal would be to be able to do this upgrade easily from a central place, such as would be the case if changing the definition of a CPP-macro and coding in terms of those macros.
Does anyone know of a good strategy (A good set of CPP macros perhaps?) for future-proofing present-day concurrency code?
[ CLARIFYING EDIT after some good comments (Thanks everyone) ]
From what I gather:
Different instances of std::shared_ptr and std::unique_ptr may be read/written, from different threads without an issue, (like when different instances are passed-in to different threads) but the object instance (or memory) they point to may NOT be safely accessed by multiple threads at the same time (so you should use a mutex, or another method to access the pointed-to-object if this is the use case). [ Thanks Alex Guteniev for that clarity ]
The SAME instance of a std::shared_ptr or std::unique_ptr may be read/written by threads in a safe way using (pre-C++20: std::atomic_load/store etc, AND post-C++20: std::atomic<std::shared_ptr> or std::atomic<std::unique_ptr> ) My thought is that this might be a place to use CPP MACROS, such as SHARED_GET, SHARED_SET, UNIQUE_GET, UNIQUE_SET that'd centralize the changes you'd need to make to go from C++17 to C++20. [ Thanks NicolBolas for the clarity on what is actually coming in C++20. As was pointed out: the link I provided in the comments below is outdated, so be careful not to consider it fact.]
If you are passing std::unique_ptr between threads using std::move to pass the pointed-to-memory along, and using queues to enforce that only a single thread has access at any given time, you can use both the std_unique pointers themselves AND the pointed-to-memory in the thread that receives the pointer without any mutexes, or other protection against resource contention.
Because of my confusion when asking, my original question was perhaps confusing. Now I'd rephrase the question as: I'm looking for a set of access CPP macros #defines, that detect C++17 and C++20 and use that version's cleanest/correct definition for the following operations:
MAKE_LOCAL_SHARED: Create/load a local std::shared_ptr instance from
a common/shared instance that the thread can read/write without
contention with the original. It should point to the same memory
that the common/shared one pointed to
BEGIN_USE_SHARED_TGT: Create an
hold a std::lock_guard/mutex within the scope of which the
pointed-to-memory of a local std::shared_ptr instance can be safely
used.
END_USE_SHARED_TGT: (probably just a closing brace?) Release
the std::lock_guard/mutex when done using the pointed-to-memory
BEGIN_USE_UNIQUE_TGT, END_USE_UNIQUE_TGT (same as above for
std::unique_ptr
I'm engaged in non-trivial multi-threaded development and need to be able to pass smart pointers (both shared and unique) between threads via (hopefully lock-free) queues and use them in various thread-safe ways.
When using a (lock-free) queue, you don't access produced and consumed elements at the same time.
For accessing different variables unique_ptr and shared_ptr are already safe. When two shared_ptrs point to the same object, there is a guarantee that manipulating these shared_ptrs in different threads is thread safe, usually implemented using reference counting. Different unique_ptrs don't point to the same object.
Just use shared_ptr and unique_ptr as usual if you just put them to a queue and don't really access the same variable from multiple threads at the same time.

What is the reason for the name `weak_ptr::lock()`?

Many of our developers don't understand what creating a shared_ptr from a weak_ptr has to do with locking things. By them, the term 'lock' is associated with mutexes first.
It could have been called use or safeguard or lease or promote for instance... but hey, it isn't, and it's our responsibility to learn the standard.
But to them, this is so bad an issue that another class that copied this idiom had to be renamed, causing numerous lines of code to follow the rename. Now we have sacrificed consistency with the standard for just a little intuitivity.
Does anyone know how the choice for the name lock() was made?
It locks the shared object in memory, and prevents it being deleted.
It has nothing to do with locking a mutex or anything like that.

How does the upgradable ownership of a mutex affect other threads?

the situation is:
a thread acquires an upgradable ownership of a boost::shared_mutex and is calling unlock_upgrade_and_lock(), which blocks because the other threads are possessing shared ownership of the same shared_mutex at the moment.
Will upgradable ownership of the first thread prevent(block) other threads when they are trying to "lock_shared" the shared_mutex so that all those already sharing ownership will finally unlock_shared and exclusive ownership for the first thread be guaranteed?
Or there is a possibility for the first thread to stay blocked for as long as there is another thread sharing the mutex?
(Assuming the Boost implementation vaguely models the WG21 proposal from Howard Hinnant ...)
Converting from shared ownership to upgrade ownership prevents any new threads acquiring the lock, so eventually all the shared owners will release it and the thread with upgrade ownership can convert it to exclusive ownership. This is the point of the "upgrade lock" as opposed to just shared and exclusive locks, see the explanation in N3427:
Note that an alternative design of try-converting from shared to exclusive, instead of from shared to upgrade as shown, would be susceptible to update (writer) starvation. This is because as long as there are multiple searchers (shared locks), none of the searchers can ever successfully try-convert to updaters. It is only by successfully registering yourself as the single thread having upgrade ownership and then blocking on a conversion from upgrade to exclusive, do you enable the implementation to start blocking new searchers from obtaining a shared lock so that you can eventually obtain the exclusive lock as existing searchers are cleared out.

Why should call lock_upgrade() of a boost::thread::shared_mutex block?

I'm studying manual on boost::thread and they say that effect of calling lock_upgrade() of shared_mutex is
"The current thread blocks until upgrade ownership can be obtained for the current thread.".
But why should it block, as upgrade ownership it's not yet an exclusive ownership?
Thank you.
But why should it block, as upgrade ownership it's not yet an exclusive ownership?
If someone already has exclusive ownership, then no-one can obtain shared or upgradable ownership; so lock_shared and lock_upgrade will both block in that situation.
And, as noted in the comments, only one thread can have upgradable ownership; so lock_upgrade will also block if someone already has that.

Difference between boost::unique_lock and boost::upgrade_lock?

I am new to threading concept in C++ . I Just wanted to know few things: How is a boost::unique_lock different from boost::upgrade_lock?
How actually an exclusive ownership differ from upgrade ownership.
Maybe one can say exclusive ownership is thread safe but not upgrade ownership,
in that case i would like to know how can an upgrade ownership can be harmful if
it can be? I want to know what is that upgrade_lock allows or not allows that
unique_lock does except exclusive lock thing. Not providing exclusive lock by
upgrade_lock makes it similar to shared_lock or what and if so then how is it
different from shared_lock?
The difference between upgrade_lock and unique_lock is simple. An instance of unique_lock is acquiring a full exclusive ownership of a shared_mutex. This means that no one else can get any type of ownership while the unique_lock is alive.
Unlike the unique_lock an instance of upgrade_lock is acquiring an upgrade ownership that exclusive only amongst threads trying to get the same upgrade ownership. All other threads that try to get a shared ownership could acquire it with no conflict until the upgrade_lock is upgraded to unique (with an instance of upgrade_to_unique_lock).
The upgrade_lock is useful when some of threads can be readers only and will not try to promote itself to writers. Otherwise (all readers may try to become writers at some point) upgrade_lock will operate as unique_lock.
#Xeo yes upgrade_lock can be upgraded to uniue_lock by
upgrade_yo_unique_lock . so why do we need shared_lock is that because
shared_lock cant be upgrade to unique_lock ? Is that the only thing ?
Yes.
shared_mutex exists only to supply the functionality of traditional read/write mutexes. Some clients will be understandably leery of a mutex changing lock-modes from one to another, and yet still need the traditional functionality of a rw/mutex. This is the role of shared_mutex.
Other clients will look at shared_mutex and say: I want more. I want to sometimes atomically upgrade my ownership from shared to exclusive. And this is the role of upgrade_mutex. upgrade_mutex is not better than shared_mutex, nor vice-versa. It is simply another tool in the toolbox.