我正在学习clojure并通过生产者消费者示例尝试其并发性和有效性。
这是否因为必须使用ref和deref以及观看和取消观看而感到非常尴尬。
我尝试检查其他代码段;但除了使用Java Condition await()和signal()方法以及Java Lock之外,还有更好的方法来重新分解这个。我不想在Java中使用任何东西。
这是代码;我想我的用法会在这里犯很多错误......
;a simple producer class
(ns my.clojure.producer
(:use my.clojure.consumer)
(:gen-class)
)
(def tasklist( ref (list) )) ;this is declared as a global variable; to make this
;mutable we need to use the fn ref
(defn gettasklist[]
(deref tasklist) ;we need to use deref fn to return the task list
)
(def testagent (agent 0)); create an agent
(defn emptytasklist[akey aref old-val new-val]
(doseq [item (gettasklist)]
(println(str "item is") item)
(send testagent consume item)
(send testagent increment item)
)
(. java.lang.Thread sleep 1000)
(dosync ; adding a transaction for this is needed to reset
(remove-watch tasklist "key123"); removing the watch on the tasklist so that it does not
; go to a recursive call
(ref-set tasklist (list ) ) ; we need to make it as a ref to reassign
(println (str "The number of tasks now remaining is=") (count (gettasklist)))
)
(add-watch tasklist "key123" emptytasklist)
)
(add-watch tasklist "key123" emptytasklist)
(defn addtask [task]
(dosync ; adding a transaction for this is needed to refset
;(println (str "The number of tasks before") (count (gettasklist)))
(println (str "Adding a task") task)
(ref-set tasklist (conj (gettasklist) task )) ; we need to make it as a ref to reassign
;(println (str "The number of tasks after") (count (gettasklist)))
)
)
这是消费者代码
(ns my.clojure.consumer
)
(defn consume[c item]
(println "In the consume method:Item is " c item )
item
)
(defn increment [c n]
(println "parmeters are" c n)
(+ c n)
)
这是测试代码(我已经使用maven运行clojure代码并使用NetBeans进行编辑,因为这对我来说比较熟悉 - 来自Java - 文件夹结构和pom at - https://github.com/alexcpn/clojure-evolve
(ns my.clojure.Testproducer
(:use my.clojure.producer)
(:use clojure.test)
(:gen-class)
)
(deftest test-addandcheck
(addtask 1)
(addtask 2)
(is(= 0 (count (gettasklist))))
(println (str "The number of tasks are") (count (gettasklist)))
)
如果任何人可以轻易地重构这些以便我能够阅读和理解代码,那么它将是伟大的;否则我想我将不得不了解更多
修改-1
我想使用一个全局任务列表并通过取消引用它(deref)使其可用于其他函数并再次通过ref使其变为不是clojure中的方式;
因此,更改addTask方法以将传入的任务直接发送给代理
(defn addtask [task]
(dosync ; adding a transaction for this is needed to refset
(println (str "Adding a task") task)
;(ref-set tasklist (conj (gettasklist) task )) ; we need to make it as a ref to reassign
(def testagent (agent 0)); create an agent
(send testagent consume task)
(send testagent increment task)
)
然而,当我测试它时
(deftest test-addandcheck
(loop [task 0]
(when ( < task 100)
(addtask task)
(recur (inc task))))
(is(= 0 (count (gettasklist))))
(println (str "The number of tasks are") (count (gettasklist)))
)
过了一段时间后我得到了Java拒绝执行异常 - 如果你做Java线程,这很好,因为你可以完全控制。但是从clojure看起来很奇怪,特别是因为你没有自己选择ThreadPool stratergy
Adding a task 85
Exception in thread "pool-1-thread-4" java.util.concurrent.RejectedExecutionExce
ption
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution
(ThreadPoolExecutor.java:1759)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.jav
a:767)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.ja
va:658)
at clojure.lang.Agent$Action.execute(Agent.java:56)
at clojure.lang.Agent$Action.doRun(Agent.java:95)
at clojure.lang.Agent$Action.run(Agent.java:106)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExec
utor.java:885)Adding a task 86
Adding a task 87
答案 0 :(得分:4)
我认为造型生产者&amp;使用lamina channels可以最简单(最有效)地完成clojure中的消费者。
答案 1 :(得分:1)
我在Computing Folder Sizes Asynchronously中使用生产者/消费者模式制作了类似的clojure程序。
我认为你真的不需要使用Refs。 您有一个可变状态的单个任务列表,您希望同步更改。变化相对较快,不依赖于任何其他外部状态,只取决于消费者的变量。
与原子一样,交换!功能可以帮助您按照我理解的方式进行更改。
您可以查看我的代码段Computing folder size我认为它至少可以证明您正确使用了原子&amp;代理商。我玩了很多,所以它应该是正确的。
希望它有所帮助!
此致 扬
答案 2 :(得分:0)
我正在查看Twitter风暴的Clojure示例。他刚用LinkedBlockingQueue。它易于使用,并发且性能良好。当然,它缺乏不可变的Clojure解决方案的性感,但它会很好用。
答案 3 :(得分:0)
我遇到过几个需要能力的用例:
我发现clojure内置的并发功能(虽然它们本身非常简单和有用)使前两个要点变得困难。 lamina
看起来很棒,但是我没有看到它能解决我的特定用例的方式,而不需要基于BlockingQueue
的实现我需要做的同样的额外管道工作
所以,我最终乱砍了一个简单的clojure库,试图解决我的问题。它基本上只是BlockingQueue
的包装器,它试图隐藏一些Java结构并提供更高级别的生产者 - 消费者API。我对API还不是很满意;它可能会进一步发展......但它可以运作:
https://github.com/cprice-puppet/freemarket
使用示例:
(def myproducer (producer producer-work-fn num-workers max-work))
(def myconsumer (consumer myproducer consumer-work-fn num-workers max-results))
(doseq [result (work-queue->seq (:result-queue myconsumer))]
(println result))
欢迎提供反馈/建议/贡献!