我使用std :: map来实现我的本地哈希表,它将由多个线程同时访问。 我做了一些研究,发现std :: map不是线程安全的。 所以我将在地图上使用互斥锁进行插入和删除操作。 我打算使用单独的互斥锁,每个映射条目一个,以便可以单独修改它们。
我是否还需要将查找操作放在关键部分? 查找操作会受插入/删除操作的影响吗? 有没有比使用可以处理所有事情的std :: map更好的实现?
答案 0 :(得分:5)
二叉树不是特别适合多线程,因为重新平衡可以在树范围的修改中退化。此外,全局互斥体将非常负面地访问性能。
我强烈建议使用已编写的线程安全容器。例如,Intel TBB包含concurrent_hash_map
。
如果您希望学习,那么这里有一些关于构建并发排序关联容器的提示(我相信完整的介绍不仅不在我的范围之内,而且在这里也不合适)
<强>读/写强>
您可能希望使用Reader / Writer Mutex而不是常规Mutex。这意味着并行化读取,而写入保持严格顺序。
自有树
您还可以构建自己的红黑或AVL树。通过每个节点使用Reader / Writer Mutex扩充树结构。这允许您仅阻止树的部分,而不是整个结构,即使在重新平衡时也是如此。 例如插入密钥足够远可以是平行的。
跳过列表
链接列表更适合并发操作,因为您可以轻松隔离已修改的区域。
Skip List建立在这种力量的基础上,但增加了结构,以便通过密钥提供O(log N)访问。
行走列表的典型方法是使用 hand hand 习惯用法,即在释放当前节点之一之前获取下一个节点的互斥锁。跳过列表添加第二个维度,因为您可以在两个节点之间潜水,从而释放它们(并让其他步行者超越您)。
实现比二叉搜索树简单得多。
<强>持久性强>
另一个有趣的部分是持久性(或半持久性)数据结构的概念,通常在函数式编程中找到。二进制搜索树特别适合它。
基本思想是永远不要更改节点(或其内容)。你这样做是通过共享一个可变的 head 来指向更高版本的。
主要优点是地图的版本始终可用。也就是说,即使另一个线程正在执行插入或删除,您也可以始终读取。此外,因为读取访问只需要一次并发读取(复制根指针时),它们几乎无锁,因此具有出色的性能。
引用计数(内在)是这些节点的朋友。
注意:树的副本非常便宜:)
我不知道C ++中并发Skip List或并发半持久二进制搜索树的任何实现。
答案 1 :(得分:1)
是的,如果插入或删除导致重新平衡,我相信find
也会受到影响。
答案 2 :(得分:1)
是 - 您需要在关键部分插入,删除和查找。有一些技术可以同时启用多个查找。
答案 3 :(得分:1)
你将需要将find放在一个关键部分,但你可能想要两个不同的锁,一个用于写入,一个用于读取。写锁是独占的,但如果没有线程保持写锁,则几个线程可以同时读取而没有任何问题。
此类实现适用于大多数STL实现,但它不符合标准。 std::map
通常使用red-black tree来实现,splay tree在读取元素时不会更改。如果地图是使用{{3}}实现的,那么树在查找期间会发生变化,并且一次只能读取一个线程。
对于大多数用途,我建议使用两把锁。
答案 4 :(得分:0)
从我所看到的,这里已经回答了类似的问题,答案也包括对这个问题的解释,以及更详细地解释线程安全性的链接。