线程安全的 std::map:锁定整个映射和单个值

use*_*404 6 c++ multithreading boost c++98

struct Data
{
 ...
 CRITICAL_SECTION valLock;
}    
std::map<int, Data> mp;
CRITICAL_SECTION mpLock;
Run Code Online (Sandbox Code Playgroud)

我目前正在使用两个关键部分来确保该线程安全。

我必须锁定两者mapData更新Data

//Lock mpLock
//Lock mp[key1].valLock
mp[key1].something = something_new;
//unlock mp[key1].valLock
//unlock mpLock
Run Code Online (Sandbox Code Playgroud)

我研究了英特尔的并发哈希图,它不需要两个锁并在内部处理这个问题。如果我不想使用英特尔的tbb,还有其他方法吗?我只有支持。不过c++ 98可以使用。boost调查过boost::shared_mutex,但无法关联如何在当前场景中使用它。

编辑:容器上的锁真的需要吗?我不能用来Data::valLock读/写Data。任何插入mp都不会影响现有的迭代器,因此不需要锁。任何删除mp都会以 开头Data::valLock。这里可能会错过什么情况?

编辑2:

UpdateThread()
{
   //Lock mp[key].valLock
   mp[key].a = b;         //Line 1
   //unlock mp[key].valLock
}

ReadThread()
{
    //Lock mp[key].valLock
   something = mp[key].a;   //Line 2
   //unlock mp[key].valLock
}
Run Code Online (Sandbox Code Playgroud)

所以我认为只有当第 1 行完成(或反之亦然)即已mp更新(以及地图内部结构)时,第 2 行才能执行。那么它不安全吗?如果不是,则意味着如果一个线程修改 mp[key1],而另一个线程读取 mp[key2],这是数据竞争吗?

Max*_*kin 4

需要一个互斥体来使容器成为线程安全的。每个对象的互斥体使每个对象都是线程安全的。

每个对象的互斥体是一个次优的设计。另一种设计是使用可复制但不可变的对象或在容器中存储共享/侵入式指针。

对于不可变对象,读取器锁定容器(以供读取),制作元素的副本并解锁容器。编写者锁定容器(用于写入)添加/删除/修改元素并解锁。因为读取器总是复制元素,所以元素上的线程之间永远不会发生争用。

对于共享指针,读者可以按照上面的方式进行操作。编写者也按照上述方式进行操作,但编写者总是创建一个新元素并替换现有元素,而不是修改现有元素。

不可变对象的示例:

template<class Key, class Value>
class ThreadSafeMap
{
    std::mutex m_;
    std::map<Key, Value> c_;

public:
    Value get(Key const& k) {
        std::unique_lock<decltype(m_)> lock(m_);
        return c_[k]; // Return a copy.
    }

    template<class Value2>
    void set(Key const& k, Value2&& v) {
        std::unique_lock<decltype(m_)> lock(m_);
        c_[k] = std::forward<Value2>(v);
    }
};
Run Code Online (Sandbox Code Playgroud)

您可能还喜欢使用std::unordered_map(或开源的)而不是为了std::map获得更好的性能。std::map对缓存相当不友好。