【问题标题】:Clojure concurrency: Let an agent act on a java object / deftypeClojure 并发:让代理作用于 java 对象/deftype
【发布时间】:2015-10-12 02:33:30
【问题描述】:

在学习 Clojure 的过程中,我遇到了以下问题:

setup: 一个图数据结构类,由deftypedefinterface 创建,具有addNode [id data] 成员函数。直接调用时按预期工作,如(.addNode graph "anItem" 14)

想法:由于字符串标记化和更新图形都消耗大量时间(至少数百万行),我想连续读取和标记文件并将标记列表推送到将执行 `(.addNode graph id data) 部分的代理。

问题:我似乎找不到正确的语法来使代理接受类实例的成员函数作为更新函数。

简化代码(此处删除了命名空间,可能包含拼写错误!):

; from graph.clj
(definterface IGraph
  (addNode [id data])
  (addNode2 [_ id data]))
(deftype Graph [^:volatile-mutable nodes] ; expects an empty map, else further calls fail horribly
  IGraph
  (addNode [this id data] (set! nodes (assoc nodes id data)) this)
  (addNode2 [this _ id data] (.addNode this id data) this))

; core.clj
(def g (Graph. {}))
(def smith (agent g))               ; agent smith shall do the dirty work

(send smith .addNode "x" 42) ; unable to resolve symbol
(send smith (.addNode @smith) "x" 42) ; IllegalArgumentException (arity?)
(send smith (.addNode2 @smith) "x" 42) ; same as above. Not arity after all?
(send smith #(.addNode @smith) "x" 42) ; ArityException of eval (3)
(send smith (partial #(.addNode @smith)) "x" 42) ; the same

; agent smith, the president is ashamed...

由于各种原因,这五行不起作用,而一个简单的

(def jones (agent 0))
(send jones + 1)

; agent jones, this nation is in your debt

成功执行。这应该是可能的,那我做错了什么?

【问题讨论】:

    标签: object concurrency clojure functional-programming agents


    【解决方案1】:

    您的直接问题是 .addNode 不是函数,而是 . 特殊形式周围的一些糖。您不能通过这种方式传递特殊形式,因此您需要将其包装在代理知道如何调用的函数中 - #(.addNode %&) 或类似的东西。然后只有在所有参数都存在时才评估特殊形式,并且它可以看到图上的第一个参数中有一个addNode 方法。

    不过,James Sharp's answer 有一个很好的观点——这是一种非常必要且面向对象的方法来处理这个问题。从到目前为止的代码来看,您似乎打算将列表中的令牌与send 连续提供给smith,然后他将通过assoc 更新他的图表。这是一个经典的 reduce 操作 - 将空图和 assoc 放入其中,将结果和 assoc 放入其中,依此类推,直到输入用完。在此过程的每个步骤之间让代理执行 STM 的事情似乎不是很有必要。

    如果您出于性能原因希望使用^:volatile-mutable,您还可以尝试使用瞬态并减少assoc! - 或者只使用为您处理瞬态的(into {} ...(尽管它的行为类似于conj,不是assoc 并且对于地图需要[key value] 的向量而不是单独的键和值参数)。

    【讨论】:

    • 感谢您的回答。我只使用这种可变设置,因为这两个任务每个都需要大约 50% 的计算时间,所以我的目标是明确的并发。我不知道如何使用瞬态进行并发,但我会阅读。有什么好的参考建议吗?
    【解决方案2】:

    您尝试做的事情是可能的,但恕我直言,这不是惯用的,您正在以 OO 的方式思考。正如docs 所说,

    Agent 本身应该是不可变的(最好是 Clojure 持久性集合之一的实例)

    您可以使用Map 为树建模,看看这个example

    一般来说4clojure 这是开始编写惯用的 Clojure 解决方案的好地方

    已编辑more complete and idiomatic example

    【讨论】:

    • 感谢您的回答。你对惯用的 Clojure 方式是正确的——我尝试了这个设置来显式地强制并发。我已经完成了koans,但感谢 4clojure 链接。在内部,该图由地图组成,尽管我担心这些地图会为未使用的存储桶浪费大量内存,这可能对任务造成致命...
    • 我在某处读到 Clojure 不可变映射占用的空间大约是 Java 等效映射的两倍,但这是您必须在特定场景中进行的利弊分析。为了提高并发性,我将文件分成块,并分叉 n 线程来完成这项工作,在最后一步,他们将依赖 agent 来更新树。或者,您可以构建单独的树并将它们组合成 map-reduce 样式。如果你想玩得更远,看看`core.async' :)
    • 嗯... atm 任务是从 60-120GB 输入文本文件中提取节点,所以我担心最终内存消耗可能是应用程序的瓶颈。此外,任何文本行都可能对任何子图有贡献,可能会启动一个新图,或者可能只是增加已经存在的边的计数器,因此拆分非常困难,因为在最坏的情况下它会增加内存消耗。尽管如此,到目前为止,您已经证明非常有帮助......我的代码变得非常 fp 风格,它在大约 80 行中表达了 600 行 c++ 代码(我正在将一个工具转换为 Clojure 进行练习......)
    • 哇...加上缺少的单词和字母-这里的键盘太糟糕了
    • 120GB 是一个值得将内存中的图形数据库视为 OrientDB 的大小,您甚至还有一个 clojure wrapper。根据我的经验,它工作得很好
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多