二叉树不是特别适合多线程,因为重新平衡会在树范围的修改中退化。此外,全局互斥锁会对性能产生非常负面的影响。
我强烈建议使用已经编写好的线程安全容器。例如,Intel TBB 包含 concurrent_hash_map。
但是,如果您希望学习,这里有一些关于构建并发排序关联容器的提示(我相信完整的介绍不仅超出我的能力范围,而且不合适,在这里) .
读取器/写入器
您可能想要使用 Reader/Writer Mutex,而不是常规的 Mutex。这意味着并行读取,而写入保持严格的顺序。
自己的树
您还可以构建自己的红黑树或 AVL 树。通过使用每个节点的 Reader/Writer Mutex 来扩充树结构。这允许您仅阻止树的部分,而不是整个结构,即使在重新平衡时也是如此。 eg 插入距离足够远的键可以是平行的。
跳过列表
链表更适合并发操作,因为您可以轻松隔离已修改区域。
Skip List 建立在这种优势之上,但增强了结构以提供 O(log N) 密钥访问。
遍历列表的典型方法是使用 hand over hand 习惯用法,即在释放当前节点之一之前获取下一个节点的互斥体。跳过列表添加了第二个维度,因为您可以在两个节点之间潜水,从而释放它们(并让其他步行者走在您前面)。
实现比二叉搜索树简单得多。
持久性
另一个有趣的部分是持久(或半持久)数据结构的概念,通常在函数式编程中找到。二叉搜索树特别适合它。
基本思想是一旦节点(或其内容)存在就永远不要改变它。你可以通过共享一个可变的head来做到这一点,它将指向更高的版本。
- 读取:复制当前头,然后放心使用(信息不可变)
- 要写入:您将在常规树中修改的每个节点都被复制并修改了副本,因此您每次都重建树的一部分(直到根),并更新 head 指向新的根。有一些有效的方法可以在下降树时重新平衡。 写入是顺序的
主要优点是地图版本始终可用。也就是说,即使另一个线程正在执行插入或删除操作,您也始终可以读取。此外,由于read 访问只需要一次并发 读取(复制根指针时),它们几乎是无锁的,因此具有出色的性能。
引用计数(内在)是这些节点的朋友。
注意:树的副本非常便宜:)
我不知道并发跳过列表或并发半持久二叉搜索树在 C++ 中的任何实现。