unordered_map线程安全

时间:2012-03-13 13:59:23

标签: c++ multithreading boost thread-safety hashmap

我正在使用boost:thread库将单个线程程序更改为多线程。该程序使用unordered_map作为hasp_map进行查找。我的问题是......

有一次,许多线程都会写入,而另一些线​​程将会读取,但不会同时读取和写入,即所有线程都将读取或者所有线程都将写入。这是线程安全的和为此设计的容器吗?如果它会,它真的会并发并提高性能吗?我需要使用一些锁定机制吗?

我在某处读到C ++标准说行为将是未定义的,但这就是全部吗?

更新:我还在考虑英特尔concurrent_hash_map。这是一个不错的选择吗?

7 个答案:

答案 0 :(得分:45)

STL容器的设计使您可以保证:

一个。多个线程同时读取

B中。一个线程同时写入

写入多个线程不是上述条件之一,也是不允许的。因此,多线程写入将创建数据争用,这是未定义的行为。

您可以使用互斥锁来解决此问题。 shared_mutex(与shared_locks结合使用)特别有用,因为这种类型的互斥锁允许多个并发读取器。

http://eel.is/c++draft/res.on.data.races#3是标准的一部分,它保证了在不同线程上同时使用const函数的能力。 http://eel.is/c++draft/container.requirements.dataraces指定了一些在不同线程上安全的其他非const操作。

答案 1 :(得分:8)

  

这是线程安全的吗?是为此设计的容器吗?

不,标准容器不是线程安全的。

  

我需要使用一些锁定机制吗?

是的,你这样做。由于你正在使用提升,boost::mutex将是一个好主意;在C ++ 11中,有std::mutex

  

我在某处读到C ++标准说行为将是未定义的,但这就是全部吗?

确实,这种行为是不确定的。我不确定你的意思是“是全部吗?”,因为未定义的行为是最糟糕的行为,而展示它的程序根据定义是不正确的。特别是,不正确的线程同步可能会导致随机崩溃和数据损坏,通常以非常难以诊断的方式进行,因此您最好不惜一切代价避免它。

  

更新:我还在考虑英特尔concurrent_hash_map。这是一个不错的选择吗?

听起来不错,但我自己从未使用它,所以我无法提出意见。

答案 2 :(得分:3)

现有答案涵盖要点:

  • 您必须有锁才能读取或写入地图
  • 您可以使用多读取器/单写入器锁来提高并发性

另外,你应该知道:

  • 使用较早检索的迭代器,或者指向地图中项目的引用或指针,计为读取或写入操作

  • 在其他线程中执行的写操作可能会使指针/引用/迭代器无效进入映射,就像它们在同一个线程中完成一样,即使在尝试继续之前再次获取锁定也是如此使用它们......

答案 3 :(得分:3)

std :: unordered_map符合Container(ref http://en.cppreference.com/w/cpp/container/unordered_map)的要求。有关容器线程安全性,请参阅:http://en.cppreference.com/w/cpp/container#Thread_safety

重点:

  • "同一容器中的不同元素可以由不同的线程同时修改"
  • "所有const成员函数可以由同一容器上的不同线程同时调用。此外,成员函数begin(),end(),rbegin(),rend(),front(),back(),data(),find(),lower_bound(),upper_bound(),equal_range(), at()和除了关联容器之外,operator []为了线程安全的目的而行为为const(也就是说,它们也可以由同一容器上的不同线程同时调用)。"

答案 4 :(得分:1)

访问unordered_map时,可以使用concurrent_hash_map或使用互斥锁。关于使用intel concurrent_hash_map的一个问题是你必须包含TBB,但你已经使用了boost.thread。这两个组件具有重叠的功能,因此使您的代码库变得复杂。

答案 5 :(得分:0)

std::unordered_map非常适合某些多线程情况。

还有other concurrent maps from Intel TBB

  • tbb:concurrent_hash_map。它支持用于插入/更新的细粒度,每键锁定,这是其他哈希表所无法提供的。但是,语法稍微有些罗word。参见full sample code。推荐。
  • tbb:concurrent_unordered_map。本质上是同一件事,一个键/值映射。但是,它的级别低得多,并且更难使用。必须提供一个哈希器,一个相等运算符和一个分配器。即使在官方的英特尔文档中,也没有任何示例代码。不推荐。

答案 6 :(得分:0)

如果您不需要unordered_map的所有功能,那么此解决方案将为您服务。它使用互斥锁来控制对内部unordered_map的访问。该解决方案支持以下方法,添加更多方法应该相当简单:

  • getOrDefault(key,defaultValue)-返回与key关联的值,如果不存在关联,则返回defaultValue。不存在关联时,不会创建地图条目。
  • contains(key)-返回一个布尔值;如果存在关联,则为true。
  • 放置(键,值)-创建或替换关联。
  • 删除(密钥)-删除密钥关联
  • associations()-返回包含所有当前已知关联的向量。

样品用量:

/* SynchronizedMap
** Functional Test
** g++ -O2 -Wall -std=c++11 test.cpp -o test
*/
#include <iostream>
#include "SynchronizedMap.h"

using namespace std;
using namespace Synchronized;

int main(int argc, char **argv) {

    SynchronizedMap<int, string> activeAssociations;

    activeAssociations.put({101, "red"});
    activeAssociations.put({102, "blue"});
    activeAssociations.put({102, "green"});
    activeAssociations.put({104, "purple"});
    activeAssociations.put({105, "yellow"});
    activeAssociations.remove(104);

    cout << ".getOrDefault(102)=" << activeAssociations.getOrDefault(102, "unknown") << "\n";
    cout << ".getOrDefault(112)=" << activeAssociations.getOrDefault(112, "unknown") << "\n";

    if (!activeAssociations.contains(104)) {
        cout << 123 << " does not exist\n";
    }
    if (activeAssociations.contains(101)) {
        cout << 101 << " exists\n";
    }

    cout << "--- associations: --\n";
    for (auto e: activeAssociations.associations()) {
        cout << e.first << "=" << e.second << "\n";
    }
}

样本输出:

.getOrDefault(102)=green
.getOrDefault(112)=unknown
123 does not exist
101 exists
--- associations: --
105=yellow
102=green
101=red

注意1:associations()方法不适用于非常大的数据集,因为它将在创建向量列表期间锁定表。

注2:我故意没有扩展unordered_map,以防止我自己意外地使用unordered_map中尚未扩展的方法,因此可能不是线程安全的。

#pragma once
/*
 * SynchronizedMap.h
 * Wade Ryan 20200926
 * c++11
 */

#include <unordered_map>
#include <mutex>
#include <vector>

#ifndef __SynchronizedMap__
#define __SynchronizedMap__

using namespace std;


namespace Synchronized {

    template <typename KeyType, typename ValueType>

    class SynchronizedMap {

    private:
        mutex sync;
        unordered_map<KeyType, ValueType> usermap;    

    public:
        ValueType getOrDefault(KeyType key, ValueType defaultValue) {
            sync.lock();

            ValueType rs;

            auto value=usermap.find(key);
            if (value == usermap.end()) {
                rs = defaultValue;
            } else {
                rs = value->second;
            }
            sync.unlock();
            return rs;
        }

        bool contains(KeyType key) {
            sync.lock();
            bool exists = (usermap.find(key) != usermap.end());
            sync.unlock();
            return exists;
        }

        void put(pair<KeyType, ValueType> nodePair) {
            sync.lock();

            if (usermap.find(nodePair.first) != usermap.end()) {
                usermap.erase(nodePair.first);
            }
            usermap.insert(nodePair);
            sync.unlock();
        }

        void remove(KeyType key) {
            sync.lock();

            if (usermap.find(key) != usermap.end()) {
                usermap.erase(key);
            }
            sync.unlock();
        }

        vector<pair<KeyType, ValueType>> associations() {
            sync.lock();
 
            vector<pair<KeyType, ValueType>> elements;

            for (auto it=usermap.begin(); it != usermap.end(); ++it) {
                pair<KeyType, ValueType> element (it->first, it->second);
                elements.push_back( element );
            }

            sync.unlock();
            return elements;
        }
    };       
}

#endif