为协调写入/读取选择正确的Clojure引用类型

时间:2012-10-03 18:31:39

标签: concurrency clojure stm

我正在为Campfire创建一个chatbot,它将当前用户列表保存在原子(defonce users (atom {}))中。

由于其简单性,我最初选择了这种引用类型,直到现在它仍然运行良好,但可能需要更改。

  1. Campfire将EnterMessageLeaveMessage个事件发送到流式api。我的机器人通过从Campfire API获取当前用户列表,然后使用新列表调用reset!原子上的users来对这些做出反应。

  2. 这些相同的进入/离开事件触发随机交互,例如从users原子中挑选一个随机用户并向其提问。

  3. 问题

    上面的数字2经常会询问刚离开的用户或从未询问刚刚输入的用户,因为users原子尚未!reset。我想我需要使用ref,但these docs说“作家永远不会阻止通勤者或读者。”事情是,我希望那位作家阻止我的读者,对吧?!

1 个答案:

答案 0 :(得分:3)

ref主要用于协调访问多个数据结构如果您只有一个身份(用户列表)那么refs的主要优势对您来说并不是真正的优势虽然它也不是真正的问题。 Refs也有成本,因为交易可能会运行多次,所以如果你的行为有副作用,比如发送消息,那么消息可以在事务重试时发送两次。您可以通过同时使用ref和代理来解决这个问题,因为从事务发送到代理的消息可以保证只发送一次并且只能使用最终提交的值。

在你的情况下,你可以通过以下方式做得很好:

  • 继续使用原子
  • 使用类似(swap! users assoc name user)之类的东西来逐步构建用户列表。
  • 在原子上使用watch function来处理状态变化,因为手表将捕获每个状态变化。

  • 切换到参考
  • 使用代理将实际消息发送给用户。
  • 使用手表可以使这更简单,但不是必需的。

引用“作家永远不会阻止通勤者或读者。”可能与你没有直接关系,虽然值得解释一下。在单个引用的情况下,只读取ref的值的线程永远不会等待,它获取当前值并继续。当有多个ref时,它可以在事务中读取它们,并保证它将从它们中获取一组一致的值,或者在它获得一组一致的值之前重新运行。需要更新值的线程同样需要重新运行,如果他们没有获得一致的值集,除非他们使用commute函数来更新它们,在这种情况下STM将知道它是安全的提交新值即使是其他一些线程也对该值执行了相同(或者也可以通信)的操作。