在并发环境中拍摄复杂可变结构的快照

时间:2011-12-17 13:12:01

标签: clojure

鉴于:各种嵌套集合的复杂结构,其中refs分散在不同的级别。

需要:一种获取此类结构快照的方法,同时允许写入在其他线程中继续发生。

因此,“读者”线程需要在单个长事务中读取整个复杂状态。 “编写者”线程同时在多个短交易中进行修改。据我所知,在这种情况下,STM引擎利用了refs历史。

这里有一些有趣的结果。例如,读者在交易开始后10秒内到达一些参考。 Writer每1秒修改一次ref。它产生10个ref历史值。如果超出了参考号:max-history的限制,则读者交易将永久运行。如果超过:min-history,交易可能会多次重新运行。

但实际上读者只需要一个ref值(第一个),而作者只需要最近的一个。历史列表中的所有中间值都是无用的。有没有办法避免这种历史过度使用?

感谢。

3 个答案:

答案 0 :(得分:1)

对我来说,拥有一个包含大量嵌套引用的大型结构有点“设计气味”。您正在有效地模拟可变对象图which is a bad idea if you believe Rich Hickey's take on concurrency

尝试一些不同的想法:

  • 在Clojure中解决这个问题的惯用方法是将状态放在一个顶级ref中,其中的所有内容都是不可变的。然后,读者可以免费获取整个并发状态的快照(甚至不需要事务)。可能很难从现在的位置重构这个,但我认为这是最好的做法。
  • 如果您只希望读者获得顶级参考的快照,您可以直接在事务之外进行解析。请注意,内部的引用可能会继续发生变异,因此这是否有用取决于您对读者的一致性要求。
  • 您可以在(dosync ...)事务中为读者和编写者执行正常操作。您可能会进行争用和事务重试,但这可能不是问题。
  • 您可以创建一个“快照”功能,快速遍历图形并取消引用事务中的所有引用,返回结果,并删除引用(或替换为新的克隆引用)。读者调用快照一次,然后在完成快照后继续完成剩下的工作。
  • 每次编写完成后,您都可以立即拍摄快照,并将其分别存储在原子中。读者可以直接使用它(即只有编写器线程直接访问实时数据图)

答案 1 :(得分:0)

您的问题的一般答案是您需要两件事:

  1. 表示系统处于“快照写入”模式的标志
  2. 用于保存系统处于快照模式时发生的所有事务的队列
  3. 如果由于快照过程不够快而导致队列溢出该怎么办,那么除了优化该过程或增加队列大小之外,您无能为力。这将是一个平衡,你必须根据你的应用程序的需要进行攻击。这是一个微妙的平衡,并将根据您的系统的复杂程度进行一些非常广泛的测试。

    但你走在正确的轨道上。如果您基本上将系统置于“快照写入模式”,那么您的读取器/写入器方法应该自动更改它们的读/写位置,以便正在进行更改的线程获取所有“当前值”并且线程读取快照状态是读取所有“快照值”。您可以将它们拆分为单独的方法 - 快照读取器将使用“快照值”方法,所有其他线程将读取“当前值”方法。

    快照阅读器完成其工作后,需要清除快照状态。

    如果线程在当前未设置“快照状态”时尝试读取“快照值”,则应仅使用“当前值”进行响应。没什么大不了的。

    允许采用文件系统快照进行备份但不阻止编写新数据的系统遵循类似的方案。

    最后,除非您需要记录所有更改到系统(即审计跟踪),否则事务队列实际上不需要是更改队列应用 - 它只需要存储您在系统中更改的任何内容的最新值。清除“快照状态”后,您只需将所有这些未提交的值写入系统,并将其调用完毕。您可能需要考虑的事情是记录尚未进行的更改,以防您需要从崩溃中恢复,并且仍然应用这些更改。日志文件将为您提供已发生事件的记录,并可让您执行此恢复。这是对恢复过程的过度简化,但这不是你的问题所在,所以我会停在那里。

答案 2 :(得分:0)

您所追求的是高性能并发中最先进的技术。你应该看看Nathan Bronson的工作,以及他与Aleksandar Prokopec,Phil Bagwell和Scala团队的实验室合作。

二叉树: http://ppl.stanford.edu/papers/ppopp207-bronson.pdf https://github.com/nbronson/snaptree/

基于数组树的哈希映射 http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf

然而,快速浏览一下上面的实现应该说服你这不是"滚动你自己"领土。如果可能的话,我会尝试根据您的需求调整现成的并发数据结构。我所连接的所有东西都可以在JVM上免费获得,但它本身并不是原生的Clojure。