8

我知道将具有副作用的函数放在 STM 事务中通常是不好的做法,因为它们可能会被重试和调用多次。

然而,在我看来,您可以使用代理来确保只有在事务成功完成后才会执行副作用。

例如

(dosync
  // transactional stuff
  (send some-agent #(function-with-side-effects params))
  // more transactional stuff
  )

这是好习惯吗?

有什么优点/缺点/陷阱?

4

3 回答 3

8

原来的:

似乎这对我有用。根据您的副作用,您可能希望使用 send-off(对于 IO-bound 操作)而不是 send(对于 cpu-bound 操作)。发送/发送会将任务排入内部代理执行程序池之一(有一个固定大小的 cpu 池和无限大小的 io ops 池)。一旦任务入队,工作就脱离了 dosync 的线程,因此您此时已断开连接。

当然,您需要从事务中捕获您需要的任何值到发送函数中。而且您需要处理由于重试而可能发生多次的发送。

更新(见评论):

在 ref 的事务中发送的代理将一直保持到 ref 事务成功完成并执行一次。所以在我上面的回答中,发送不会发生多次,但是它不会在 ref 事务期间发生,这可能不是你想要的(如果你希望记录或做副作用的事情)。

于 2011-01-22T17:07:58.127 回答
6

这是有效的,并且是常见的做法。但是,就像 Alex 正确指出的那样,您应该考虑发送而不是发送。

有更多方法可以捕获提交的值并将它们从事务中取出。例如,您可以在矢量(或地图或其他)中返回它们。

(let [[x y z] (dosync
                ; do stuff
                [@x @y @z])] ; values of interest to sode effects
  (side-effect x y z))

或者你可以打电话重置!在本地原子上(当然是在 dosync 块的词法范围之外定义的)。

于 2011-01-23T17:23:45.630 回答
1

使用代理没有什么问题,但简单地从副作用计算所需的事务值返回通常就足够了。

Refs 可能是最干净的方法,但你甚至可以只用原子来管理它!

(def work-queue-size (atom [0]))

(defn add-job [thunk]
  (let [[running accepted?]
        (swap! work-queue-size
               (fn [[active]]
                 (if (< active 3)
                   [(inc active) true]
                   [active false])))]
    (println
     (str "Your job has been "
          (if accepted?
            "queued, and there are "
            "rejected - there are already ")
          running
          " total running jobs"))))

可以根据swap!需要重试多次,但工作队列永远不会超过三个,并且您将始终只打印一次与您的工作项的接受正确绑定的消息。“原始设计”只需要原子中的一个 int,但您可以将其变成一对,以便将有趣的数据从计算中传回。

于 2011-05-12T16:21:18.827 回答