【问题标题】:How should I handle thread synchronisation in a shared tree data structure?我应该如何处理共享树数据结构中的线程同步?
【发布时间】:2010-12-01 09:13:36
【问题描述】:

这只是为了一种并发复习...

想象一下我在内存中有一个B+树数据结构——每个节点多个项目,只有叶节点包含项目,叶节点也形成一个链表,方便顺序访问。插入和删除大多只影响叶节点,但会导致节点在可能传播到根的进程中分裂或合并。

我有一个单线程实现,更新遵循一种预先计划的方法。只要节点需要更改,递归就会从叶级向上逐步上升,构建一个描述所需更改的链表(链接不同递归中的局部变量)。当它知道需要什么时,它可以检查它是否可以分配所有需要的节点,并在退出递归之前通过引用此计划来应用所有需要的更改(或不应用)。

此实现还在更新时“维护”迭代器,因此迭代器不会因插入/删除而失效,除非它们指向的特定项目被删除。在同一节点内插入/删除会导致指向该节点的迭代器被更新。

问题是,我需要让它成为多线程的——同时支持潜在的许多读者和作者。

我希望多个阅读器能够同时读写,只要不存在损坏的风险。因此,对于阅读,我根本不想要互斥访问,即使是对单个节点也是如此。对于写作,我想锁定更改所需的最小节点数。当然,我想避免僵局。

谢天谢地,这不是我真正需要做的事情 - 但由于我忽略了我的并发技能,这似乎是一个很好的思想实验。

这显然类似于数据库和文件系统必须处理的各种问题,所以我猜我可能会得到一些关于这类事情的参考,这会很棒。

那么 - 我将如何处理线程同步呢?我可以隐约看到节点上互斥锁和/或信号量的作用,但我会使用什么策略来处理它们?

【问题讨论】:

    标签: concurrency


    【解决方案1】:

    绝对具有挑战性的任务!我看到你是 c++ 程序员,但是我相信在 c++ 中有与 java 中类似的概念,我会尝试从 java 的角度来提供帮助。

    所以对于阅读,我根本不希望互斥访问,即使是单个节点

    您可以使用 ReadWriteLock。只要没有写入者,它就会被多个读取线程同时持有。写锁是独占的。你只需要在写作时使用独占访问。你有 c++ 中的类比吗?

    当然,我想避免死锁。

    只需按级别顺序(例如从上到下)锁定多个节点。这将保证您免受死锁(这将类似于 Lamport 的面包店算法)。

    至于数据库 - 它们通过杀死一个进程来解决死锁 :-)。

    另一种策略是以类似于 Cliff Click 实现解锁哈希图的方式实现解锁树结构(涵盖所有情况的状态机): video

    干杯

    【讨论】:

    • 锁定顺序是更新中读写节点的混合。从纯粹的写入意义来看,自上而下的锁定没有死锁意义 - 但在插入或删除到某个位置(不仅仅是一个键)时,您首先确定哪些节点受到影响,从叶子中的项目开始(并且大概因此读取锁定自下而上)。一个键插入,你当然首先有一个自上而下的搜索。请注意,也许自下而上的锁定可以工作 - 如果一致的话。
    • 至于 ReadWriteLock - C++ 没有标准的同步原语,尽管每天都有很多人使用非标准的原语。但我正在寻找更多的是 ReadWriteLock 是如何实现的。我刚刚看了一下,这两个底层锁是有道理的。
    • 在一致锁定方向上,我想知道释放你的锁有多实用(当你自然需要以错误的顺序获取一些锁时),以正确的顺序重新锁定,然后检查、修复或从头开始重做更新计划,如果这些节点在解锁时发生重大变化。从软件事务内存中窃取一个想法。仅仅因为某些事情可能会出错并不一定意味着你跳过箍来防止它。如果概率足够低,只要你能在它发生时发现并修复/重做,这没什么大不了的。
    • 虽然我无法回答上述所有问题,但我可以说拥有可扩展的并发树结构是已知问题。我已经研究了我们在 java 中的内容 - 并且有 ConcurrentSkipListMap(基本上是使用跳过列表实现的 TreeMap)。也许这与您需要的相似?无论如何,这个类是由 Doug Lea 编写的,有很多 od 文档值得一看。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-03
    • 1970-01-01
    相关资源
    最近更新 更多