I need to make a thread-safe map, where I mean that each value must be independently mutexed. For example, I need to be able to get map["abc"]
and map["vf"]
at the same time from 2 different threads.
My idea is to make two maps: one for data and one for mutex for every key:
class cache
{
private:
....
std::map<std::string, std::string> mainCache;
std::map<std::string, std::unique_ptr<std::mutex> > mutexCache;
std::mutex gMutex;
.....
public:
std::string get(std::string key);
};
std::string cache::get(std::string key){
std::mutex *m;
gMutex.lock();
if (mutexCache.count(key) == 0){
mutexCache.insert(new std::unique_ptr<std::mutex>);
}
m = mutexCache[key];
gMutex.unlock();
}
I find that I can't create map from string to mutex, because there is no copy constructor in std::mutex
and I must use std::unique_ptr
; but when I compile this I get:
/home/user/test/cache.cpp:7: error: no matching function for call to 'std::map<std::basic_string<char>, std::unique_ptr<std::mutex> >::insert(std::unique_ptr<std::mutex>*)'
mutexCache.insert(new std::unique_ptr<std::mutex>);
^
How do I solve this problem?
TL;DR: just use operator []
like std::map<std::string, std::mutex> map; map[filename];
Why do you need to use an std::unique_ptr
in the first place?
I had the same problem when I had to create an std::map
of std::mutex
objects. The issue is that std::mutex
is neither copyable nor movable, so I needed to construct it "in place".
I couldn't just use emplace
because it doesn't work directly for default-constructed values. There is an option to use std::piecewise_construct
like that:
map.emplace(std::piecewise_construct, std::make_tuple(key), std::make_tuple());
but it's IMO complicated and less readable.
My solution is much simpler - just use the operator[]
- it will create the value using its default constructor and return a reference to it. Or it will just find and return a reference to the already existing item without creating a new one.
std::map<std::string, std::mutex> map;
std::mutex& GetMutexForFile(const std::string& filename)
{
return map[filename]; // constructs it inside the map if doesn't exist
}
Replace mutexCache.insert(new std::unique_ptr<std::mutex>)
with:
mutexCache.emplace(key, new std::mutex);
In C++14, you should say:
mutexCache.emplace(key, std::make_unique<std::mutex>());
The overall code is very noisy and inelegant, though. It should probably look like this:
std::string cache::get(std::string key)
{
std::mutex * inner_mutex;
{
std::lock_guard<std::mutex> g_lk(gMutex);
auto it = mutexCache.find(key);
if (it == mutexCache.end())
{
it = mutexCache.emplace(key, std::make_unique<std::mutex>()).first;
}
inner_mutex = it->second.get();
}
{
std::lock_guard<std::mutex> c_lk(*inner_mutex);
return mainCache[key];
}
}
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