如何在Clojure中取消引用未来?

时间:2013-12-02 19:36:31

标签: clojure

我的程序如下工作:两个期货在一些随机等待时间之后将一个特定金额从一个余额A转移到余额B.每次重复做10次。所以我尝试停止程序并解决未来,但它不起作用,它抛出NullPointerException,因为我deref它。我做错了什么?

我想要一个最终结果,在程序结束前的最后一行显示balanceA和balanceB。现在它确实输出结果,但它出现在"转移"并且"尝试"。我该怎么办?

谢谢。

  (def balanceA (ref 1000))
  (def balanceB (ref 2000))
  (def agentCount (agent 1))

   ;;transfer function
  (defn transfer [balanceA balanceB amount futureNum waitingTime]
    (dosync(alter balanceA - amount))
    (Thread/sleep waitingTime)
    (dosync(alter balanceB + amount))
    (do(
        (println "Trying" futureNum waitingTime)
        (println "Transfered" futureNum @agentCount)
        (send-off agentCount inc))))

  ;;futureA 
  (dotimes [n 10](def futureA (future (transfer balanceA balanceB 20 1 (rand-int 100)))))

  ;;futureB
  (dotimes [n 10](def futureB (future (transfer balanceA balanceB 15 2 (rand-int 40))(prn "result" @balanceB @balanceB))))

  ;;print out the result
  (println "result" @balanceA @balanceB)

  ;;dereference futures
  (@futureA)
  (@futureB

这是我的输出:

  user=> (load-file "assignment10.clj")
  Result: Trying: Trying: 2650  2000
  2 Trying: 2 Trying: 2 3
   Transfered: 2 1

  user=> Trying: 1 28
  Transfered: 1 2
  Trying: 1 28
   Transfered: 1 3
  Trying:Trying:  21  2218

  Transfered: 2 4
  Transfered: 1 4
 97

  Transfered: 2 6
  10
   Trying: 2 26
  Transfered: 2 7
  Transfered: 2 Trying: 8
  2Trying:Transfered:   227
  2 28
  Transfered:7
  Transfered: 2 9
  2 9
  Trying: 2 33
  Transfered: 2 12
  Trying: 1 49
   Trying: 2 39
  Transfered:Transfered:  21  1313

  Trying: 1 57      
  Transfered: 1 15
  Trying: 1 71
  Transfered: 1 16
  Trying: 1 76
  Transfered: 1 17
  Trying: 1 81
  Transfered: 1 18
  Trying: 1 93
  Transfered: 1 19
  Trying: 1 98
  Transfered: 1 20

2 个答案:

答案 0 :(得分:6)

有几个地方你有一些额外的(),其中任何一个都可能导致NullPointerExceptions

(@futureA)

表示首先从ref获取当前值,然后获取该值并将其作为函数调用。

(do(
    (println "Trying" futureNum waitingTime)
    (println "Transfered" futureNum @agentCount)
    (send-off agentCount inc))))

()之后do的额外集合导致它将打印结果称为“尝试”作为函数,谁的第一个参数是打印“转移”的结果,第二个参数是调用send-off的结果。 由于println始终返回nil,因此会产生NPE 。这些()似乎是无意的。

除了顶级表单之外,调用def是不常见的(除非您正在编写宏)。在这种情况下,只有futureA和futureB的最后一次出现才有用。可能值得考虑使用for代替然后保存序列中所有期货的结果,以便您可以看到其中任何是否失败,而不是仅仅能够判断是否最后一个是失败的。

答案 1 :(得分:1)

对于那段代码,还有许多事情可能被认为是有问题的。我会稍微清理一下,给你一些如何解决这类问题的参考。希望你不介意。

明确自己想做的事情:

  1. 打印随机等待时间。
  2. balanceA减少一些。
  3. 等待上述时间。
  4. balanceB增加相同金额。
  5. 打印传输的数据量。
  6. 显然,你有两个“活动部件” - 天平 - 这些是相互独立的。如果您阅读this blog post之类的内容,您会发现此处状态管理的正确选择是atom。让我们使用上述方案对交易延迟进行建模:

    (def balance-a (atom 1000))
    (def balance-b (atom 2000))
    
    (defn transfer!
      [id amount delay-ms]
      (println id ": transferring with delay of" delay-ms "milliseconds ...") ;; 1.
      (swap! balance-a - amount)                                              ;; 2.
      (Thread/sleep delay-ms)                                                 ;; 3.
      (swap! balance-b + amount)                                              ;; 4.
      (println id ": transfer done."))                                        ;; 5.
    

    您希望使用不同值调用此函数的线程。 (我希望产生Thread不受未来线程池大小的限制,但future在这种情况下也适用。)你不需要取消引用它的未来run - 仅当你想等待它完成时。

    这意味着如果你想等待你创建的所有期货,你必须将它们存储在某个地方。 (在您的情况下,使用def不仅是单一的,而且还只保留最后创建的未来。)

    (defn start-transfer!
      [n id amount max-delay-ms]
      (doall
        (for [_ (range n)]
          (future (transfer! id amount (rand-int max-delay-ms))))))
    

    这将创建执行转移的期货清单(实际上是 seq )。您可以存储此列表,然后取消引用单个元素以等待所有事务完成:

    (def futures-a (start-transfer! 10 :first  20 100))
    (def futures-b (start-transfer! 10 :second 15 40))
    
    (doseq [f futures-a] @f)
    (doseq [f futures-b] @f)
    

    现在,在这一点上,没有任何东西在运行。然后,您可以阅读余额:

    (println "balances:" @balance-a "vs." @balance-b)
    

    请注意,由于不同的println语句会严重干扰彼此,因此您的控制台上的输出会非常混乱。这是,例如,为什么你的原始粘贴中有Transfered:Transfered: 21 1313这样的行。

    这里有很多,但我认为如果你花时间思考这篇文章中提到的不同内容,你至少会有一些有用的见解。