[英]Map of mutex c++11
我需要制作一个线程安全映射,我的意思是每个值都必须独立互斥。 例如,我需要能够同时从 2 个不同的线程获取map["abc"]
和map["vf"]
。
我的想法是制作两张地图:一张用于数据,一张用于每个键的互斥锁:
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();
}
我发现我无法创建从字符串到互斥锁的映射,因为std::mutex
没有复制构造函数,我必须使用std::unique_ptr
; 但是当我编译这个时,我得到:
/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>);
^
我该如何解决这个问题?
TL;DR:只需使用operator []
就像std::map<std::string, std::mutex> map; map[filename];
std::map<std::string, std::mutex> map; map[filename];
为什么首先需要使用std::unique_ptr
?
当我不得不创建std::mutex
对象的std::map
时,我遇到了同样的问题。 问题是std::mutex
既不可复制也不可移动,所以我需要“就地”构建它。
我不能只使用emplace
因为它不能直接用于默认构造的值。 有一个选项可以像这样使用std::piecewise_construct
:
map.emplace(std::piecewise_construct, std::make_tuple(key), std::make_tuple());
但它的 IMO 复杂且可读性较差。
我的解决方案要简单得多- 只需使用operator[]
- 它将使用其默认构造函数创建值并返回对它的引用。 或者它只会查找并返回对现有项目的引用,而不创建新项目。
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
}
将mutexCache.insert(new std::unique_ptr<std::mutex>)
替换为:
mutexCache.emplace(key, new std::mutex);
在 C++14 中,你应该说:
mutexCache.emplace(key, std::make_unique<std::mutex>());
但是,整体代码非常嘈杂且不优雅。 它应该看起来像这样:
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];
}
}
如果您可以访问 c++17,则可以使用 std::map::try_emplace 而不是使用指针,它应该适用于不可复制和不可移动类型!
您的互斥锁实际上不保护值。 它们在从get
返回之前被释放,然后其他线程可以第二次获得对同一字符串的引用。 哦,但是您的缓存返回字符串的副本,而不是引用。 因此,用自己的互斥锁保护每个字符串是没有意义的。
如果您想保护cache
类免受并发访问,只有gMutex
就足够了。 代码应该是
class cache
{
private:
std::map<std::string, std::string> mainCache;
std::mutex gMutex;
public:
std::string get(const std::string & key);
void set(const std::string & key, const std::string & value);
};
std::string cache::get(const std::string & key) {
std::lock_guard<std::mutex> g_lk(gMutex);
return mainCache[key];
}
void cache::set(const std::string & key, const std::string & value) {
std::lock_guard<std::mutex> g_lk(gMutex);
mainCache[key] = value;
}
如果您想为许多线程提供一种方法来与映射中的字符串实例同时工作并保护它们免受并发访问,事情就会变得更加棘手。 首先,您需要知道线程何时完成处理字符串并释放锁。 否则一旦被访问的值被永远锁定,其他线程就无法访问它。
作为可能的解决方案,您可以使用一些类
#include <iostream>
#include <string>
#include <map>
#include <mutex>
#include <memory>
template<class T>
class SharedObject {
private:
T obj;
std::mutex m;
public:
SharedObject() = default;
SharedObject(const T & object): obj(object) {}
SharedObject(T && object): obj(std::move(object)) {}
template<class F>
void access(F && f) {
std::lock_guard<std::mutex> lock(m);
f(obj);
}
};
class ThreadSafeCache
{
private:
std::map<std::string, std::shared_ptr<SharedObject<std::string>>> mainCache;
std::mutex gMutex;
public:
std::shared_ptr<SharedObject<std::string>> & get(const std::string & key) {
std::lock_guard<std::mutex> g_lk(gMutex);
return mainCache[key];
}
void set(const std::string & key, const std::string & value) {
std::shared_ptr<SharedObject<std::string>> obj;
bool alreadyAssigned = false;
{
std::lock_guard<std::mutex> g_lk(gMutex);
auto it = mainCache.find(key);
if (it != mainCache.end()) {
obj = (*it).second;
}
else {
obj = mainCache.emplace(key, std::make_shared<SharedObject<std::string>>(value)).first->second;
alreadyAssigned = true;
}
}
// we can't be sure someone not doing some long transaction with this object,
// so we can't do access under gMutex, because it locks all accesses to all other elements of cache
if (!alreadyAssigned) obj->access([&value] (std::string& s) { s = value; });
}
};
// in some thread
void foo(ThreadSafeCache & c) {
auto & sharedString = c.get("abc");
sharedString->access([&] (std::string& s) {
// code that use string goes here
std::cout << s;
// c.get("abc")->access([](auto & s) { std::cout << s; }); // deadlock
});
}
int main()
{
ThreadSafeCache c;
c.set("abc", "val");
foo(c);
return 0;
}
当然,这些类的真正实现应该有更多的方法提供更复杂的语义,考虑到常量性等等。 但我希望主要思想是明确的。
编辑:
注意:应使用 shared_ptr 到 SharedObject 因为在持有锁时不能删除互斥锁,因此如果值类型是 SharedObject 本身,则无法安全删除映射条目。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.