无序映射线程安全性

unordered_map thread safety

本文关键字:安全性 线程 映射 无序      更新时间:2023-10-16

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

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

我在某个地方读到C++标准说行为将是未定义的,但仅此而已吗?

更新:我也在考虑英特尔concurrent_hash_map。这是个好选择吗?

STL容器的设计确保您能够拥有:

A。多个线程同时读取

B。一个线程同时写入

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

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

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

std::unordereded_map满足Container(refhttp://en.cppreference.com/w/cpp/container/unordered_map)。有关容器螺纹安全,请参阅:http://en.cppreference.com/w/cpp/container#Thread_safety.

要点:

  • "同一容器中的不同元素可以由不同的线程同时修改"
  • "所有const成员函数都可以由同一容器上的不同线程同时调用。此外,为了线程安全,成员函数begin()、end()、rbegin()、rend()、front()、back((也就是说,它们也可以由同一容器上的不同线程并发调用)。"

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

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

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

是的。既然你在使用boost,boost::mutex将是个好主意;在C++11中,存在std::mutex

我在某个地方读到C++标准说行为将是未定义的,但仅此而已吗?

事实上,这种行为是不明确的。我不确定你所说的"就这些吗?"是什么意思,因为未定义的行为是最糟糕的行为,而一个展示这种行为的程序从定义上来说是不正确的。特别是,不正确的线程同步可能会导致随机崩溃和数据损坏,通常情况下很难诊断,因此明智的做法是不惜一切代价避免这种情况。

更新:我也在考虑英特尔concurrent_hash_map。这是个好选择吗?

这听起来不错,但我自己从来没有用过,所以我不能提供意见。

现有的答案涵盖了要点:

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

此外,您应该注意:

  • 使用先前检索到的迭代器,或映射中项目的引用或指针,算作读或写操作

  • 在其他线程中执行的写操作可能会使映射中的指针/引用/迭代器失效,就像在同一线程中执行一样,即使在尝试继续使用它们之前再次获取了锁。。。

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

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

还有其他来自英特尔TBB:的并发映射

  • tbb:concurrent_hash_map。它支持细粒度的、按密钥的插入/更新锁定,这是其他哈希映射所不能提供的。然而,语法稍微有些冗长。请参阅完整的示例代码。推荐
  • tbb:concurrent_unordered_map。它本质上是一样的,一个键/值映射。然而,它的级别要低得多,而且更难使用。必须提供一个散列器、一个相等运算符和一个分配器。任何地方都没有示例代码,即使在英特尔的官方文档中也是如此。不推荐

如果您不需要unordered_map的所有功能,那么这个解决方案应该适合您。它使用互斥来控制对内部无序映射的访问。该解决方案支持以下方法,添加更多应该相当容易:

  • getOrDefault(键,defaultValue)-返回与键关联的值,如果不存在关联,则返回defaultValue。当不存在关联时,映射条目不是创建的
  • contains(key)-返回布尔值;如果关联存在,则为true
  • put(key,value)-创建或替换关联
  • remove(key)-删除键的关联
  • 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 existn";
    }
    if (activeAssociations.contains(101)) {
        cout << 101 << " existsn";
    }
    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,以防止我自己意外地使用了一个未扩展的方法,该方法可能不安全。

#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