Clojure中安全的多线程计数器管理

时间:2015-07-29 20:17:39

标签: multithreading clojure

我有一个场景,我想用简单的计数器来监控不同模块的性能。代码是用clojure编写的。在运行期间我需要监视一些未知数量的可能计数器,偶尔我会报告它们(对于statsd)。

这是我的代码:

     (defn counter-incrementer []
      (let [counters (atom {})
            atom-increment (fn [counters-unwrapped metric-name metric-value]
                             (assoc counters-unwrapped metric-name (+ (get counters-unwrapped metric-name 0) metric-value)))
            increment (fn [metric-name metric-value]
                (swap! counters atom-increment metric-name metric-value))]
        (fn [metric-name metric-value]
          (increment metric-name metric-value))))

然后在我要更新计数器的代码中的每个位置,我将使用:

      (def inc-fn (counter-incrementer))
       .
       .
       .
      (inc-fn "number of logged users" 10)

此代码有效,但我觉得这不是问题的最佳解决方案。例如,每次我想更新一个计数器时,我都会锁定所有计数器地图。

对于clojure中的这类问题,是否有最佳实践解决方案?

2 个答案:

答案 0 :(得分:2)

每个计数器都有一个原子是正确的最佳解决方案。

尽管如此,最好的解决方案是采用生产就绪指标库,例如https://github.com/sjl/metrics-clojure

以下是计数器的用法: http://metrics-clojure.readthedocs.org/en/latest/metrics/counters.html

答案 1 :(得分:0)

如果要在一个引用类型中保留多个指标并仅使用clojure,则可以使用代理映射执行此操作。代理程序的更新由代理程序线程池排队和处理,因此调用线程没有锁定。

(def metrics (agent {:counter1 0,:counter2 0}))
(send metrics update-in [:counter1] inc)
@metrics
=> {:counter1 1, :counter2 0}

这样,您可以在需要时动态创建新的键值对。 update-in会在不在地图中时创建新密钥,但您需要调整更新功能以考虑零值。这可以通过将原始函数与fnil和默认值合成来实现。

(send metrics update-in [:counter3] (fnil inc 0))
@metrics
=> {:counter1 1, :counter3 1, :counter2 0}
(send metrics update-in [:counter3] (fnil inc 0))
@metrics
=> {:counter1 1, :counter3 2, :counter2 0}

您需要考虑到更新不会直接应用。如果代理池上仍有排队的操作,则会阻止关闭JVM进程,除非给出(shutdown-agents)