tbb:concurrent_hash_map <K,V>:英特尔线程构建模块(TBB)的示例代码

时间:2020-03-08 09:30:29

标签: c++ multithreading concurrency c++17 tbb

正在查找示例代码以使用来自英特尔线程构建模块(TBB)的tbb::concurrent_hash_map<K,V>

我可以插入,但似乎无法读回值。

示例代码方面似乎缺少official Intel documentation

更新

最好的文档在Voss的“ Pro TBB:带有线程构建块的C ++并行编程”中。 Download this book for free(是公共领域)。

忽略英特尔文档。它们本质上是功能签名的集合。

1 个答案:

答案 0 :(得分:2)

Intel TBB是开源的,在GitHub上是

https://github.com/intel/tbb

要安装TBB,我使用了vcpkg,它与LinuxWindowsMac兼容。是的,vcpkg来自微软,但是它是100%跨平台的,开源的并且非常流行。

Linux:

./vcpkg search tbb              # Find the package.
./vcpkg install tbb:x64-linux   # Install the package.

Windows:

vcpkg search tbb                # Find the package.
vcpkg install tbb:x64-windows   # Install the package.

编译:

  • 与任何现代编译器兼容,包括MSVC,GCC,LLVM,英特尔编译器(ICC)等。我为CMake使用了gcc

也可以下载源代码并将标头和库提取到源代码树中,这同样有效。

代码。

#include "tbb/concurrent_hash_map.h" // For concurrent hash map.

tbb::concurrent_hash_map<int, string> dict;
typedef tbb::concurrent_hash_map<int, string>::accessor dictAccessor; // See notes on accessor below.   

print("  - Insert key, method 1:\n");   
dict.insert({1,"k1"});
print("    - 1: k1\n");

print("  - Insert key, method 2:\n");
dict.emplace(2,"k2");
print("    - 2: k2\n");

string result;

{
    print("  - Read an existing key:\n");   
    dictAccessor accessor;
    const auto isFound = dict.find(accessor, 2);
    // The accessor functions as:
    // (a) a fine-grained per-key lock (released when it goes out of scope).
    // (b) a method to read the value.
    // (c) a method to insert or update the value.
    if (isFound == true) {
        print("    - {}: {}\n", accessor->first, accessor->second);
    }
}

{
    print("  - Atomically insert or update a key:\n");  
    dictAccessor accessor;
    const auto itemIsNew = dict.insert(accessor, 4);
    // The accessor functions as:
    // (a) a fine-grained per-key lock (released when it goes out of scope).
    // (b) a method to read the value.
    // (c) a method to insert or update the value.
    if (itemIsNew == true) {
        print("    - Insert.\n");
        accessor->second = "k4";
    }
    else {
        print("    - Update.\n");
        accessor->second = accessor->second + "+update";
    }
    print("    - {}: {}\n", accessor->first, accessor->second);     
}

{
    print("  - Atomically insert or update a key:\n");          
    dictAccessor accessor;
    const auto itemIsNew = dict.insert(accessor, 4);
    // The accessor functions as:
    // (a) a fine-grained per-key lock which is released when it goes out of scope.
    // (b) a method to read the value.
    // (c) a method to insert or update the value.
    if (itemIsNew == true) {
        print("    - Insert.\n");
        accessor->second = "k4";
    }
    else {
        print("    - Update.\n");
        accessor->second = accessor->second + "+update";
    }
    print("    - {}: {}\n", accessor->first, accessor->second);     
}

{
    print("  - Read the final state of the key:\n");            
    dictAccessor accessor;
    const auto isFound = dict.find(accessor, 4);
    print("    - {}: {}\n", accessor->first, accessor->second);
}

打印使用{fmtlib}进行打印;可以替换为cout <<

输出:

- Insert key, method 1:
  - 1: k1
- Insert key, method 2:
  - 2: k2
- Read an existing key:
  - 2: k2
- Atomically insert or update a key:
  - Insert.
  - 4: k4
- Atomically insert or update a key:
  - Update.
  - 4: k4+update
- Read the final state of the key:
  - 4: k4+update

其他哈希图

  • 请参阅:https://tessil.github.io/2016/08/29/benchmark-hopscotch-map.html
  • 请参阅:std::unordered_map。它具有更标准的API,并且在许多情况下是线程安全的,请参阅:unordered_map thread safety。如果可能的话,建议使用它,因为它具有更简单的API。
  • 还有英特尔TBB的concurrent_unordered_map。本质上是同一件事,一个键/值映射。但是,它更旧,水平更低,更难使用。必须提供一个哈希器,一个相等运算符和一个分配器。即使在官方的英特尔文档中,也没有任何示例代码。尽管经过数月的尝试,但我从未成功。它可能已过时,因为该免费书中没有提及(仅涵盖concurrent_hash_map)。不推荐。

更新:读取器/写入器锁

实际上有两个访问器,一个是读锁,一个是写锁:

  • const_accessor
  • accessor

如果使用find,请使用const_accessor,这是一个读锁。如果使用inserterase,请使用accessor,这是一个写锁(即它将等待直到完成所有读取,然后阻止进一步的读取,直到完成)。

这实际上等效于reader/writer lock,但是在字典中只有一个字典键,而不是整个字典。

更新

学习曲线的最后部分:对于键写操作,直到访问器超出范围才发生任何事情。因此,可以使用CAS(比较和交换)来保留不超过几条机器指令的任何锁。

将此与数据库进行比较,访问器的范围就像一个事务。当访问器超出范围时,整个事务将提交给哈希映射。

更新

上面提到的免费书在concurrent_hash_map的一章中有出色的性能提示。

结论

此哈希映射的API功能强大,但有些尴尬。但是,它支持在插入/更新时进行细粒度的每键锁定。使用CAS仅保留少数机器指令的所有锁。这是其他任何语言都无法用任何语言提供的哈希图。为了简单起见,建议从std::unordered_map开始;是thread safe as long as the two threads do not write to the same key。如果需要极快的性能,则可以选择重构或使用[]访问器和insert_or_update()在顶部编写兼容的包装。

相关问题