C++'s std::mutex
does not have a move constructor. There is a good reason for that. Basically, move constructors themselves are not generally thread safe, and the whole point of a mutex is that multiple threads will be trying to access it simultaneously.
An unfortunate implication of this is that a mutex cannot be placed into a container directly. Containers need the ability to safely move their contents around, and you can't do that with a mutex.
The easy way out is to just protect the entire container with a single separate mutex. But suppose I want finer-grained control than that? If I'm implementing a database via a container (eg: std::map
), it seems reasonable to want the ability to lock individual records, not just the whole database.
The next thing that comes to mind is to hack around the problem by using std::unique_ptr
. That would compile, but it doesn't really change the basic problem, does it? The scenario where there's a problem with move is where thread1
makes a container change that causes an entry move while thread2
is in the middle of using that container entry. In this scenario, thread2
could just as easily end up holding a destructed entry or smart pointer. It seems like no matter what, you end up having to lock the entire container with a mutex before doing anything.
It seems like there ought to be a known idiom for doing these kinds of things.
Shared mutexes and locks are an optimization for read-only pieces of multi-threaded code. It is totally safe for multiple threads to read the same variable, but std::mutex can not be locked by multiple threads simultaneously, even if those threads only want to read a value.
The idea behind mutexes is to only allow one thread access to a section of memory at any one time. If one thread locks the mutex, any other lock attempts will block until the first one unlocks. However, how is this implemented? To lock itself, the mutex has to set a bit somewhere that says that it is locked.
Example 4-1 Mutex Lock ExampleThe get_count() function uses the mutex lock to guarantee that the 64-bit quantity count is read atomically. On a 32-bit architecture, a long long is really two 32-bit quantities. Reading an integer value is an atomic operation because integer is the common word size on most machines.
A mutex is initialized in the beginning of the main function. The same mutex is locked in the 'trythis()' function while using the shared resource 'counter'. At the end of the function 'trythis()' the same mutex is unlocked. At the end of the main function when both the threads are done, the mutex is destroyed.
The mutex does not require to be moved:
Imagine that every row in your map is like:
template <class T>
class row
{
shared_ptr<mutex> m;
T data;
...
};
So if your row need to be moved or copied, there is no problem.
Then, you may access the mutex from every process to access the data.
Of course, you need a global mutex to perform changes on the whole map: insert / delete / [] / any other operation that change the state of the map.
EDITED:
Following a simple example of code with a mutex in every row. (It does not implement anything else that just the data structure)
#include <memory>
#include <map>
#include <mutex>
template <class T>
class row
{
std::shared_ptr<std::mutex> m;
T data;
public:
row( std::shared_ptr<std::mutex> mut): m(mut){};
};
auto main () -> int
{
std::shared_ptr<std::mutex> mut(new std::mutex);
std::map<int,row<int>> db;
row<int> a(mut);
db.insert(std::pair<int, row<int>>(1, a));
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With