Clojure Producer Consumer

时间:2012-01-19 11:35:33

标签: clojure

我正在学习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

4 个答案:

答案 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))

欢迎提供反馈/建议/贡献!