Можно ли контролировать уровень конкуренции STM?

Есть ли способ узнать, повторяются ли транзакции Clojure STM и с какой скоростью?


person deprecated    schedule 18.06.2013    source источник
comment
См. также: stackoverflow.com/questions/4792197/   -  person noahlz    schedule 19.06.2013


Ответы (3)


Вы можете наблюдать history count ссылки, которая будет указывать на наличие разногласий по нему:

user=> (def my-ref (ref 0 :min-history 1))
#'user/my-ref
user=> (ref-history-count my-ref)
0
user=> (dosync (alter my-ref inc))
1
user=> (ref-history-count my-ref)
1

Счетчик истории напрямую не представляет конкуренцию. Вместо этого он представляет количество прошлых значений, которые были сохранены для обслуживания параллельных операций чтения.

Размер истории ограничен значениями min и max. По умолчанию это 0 и 10 соответственно, но вы можете изменить их при создании ref (см. выше). Поскольку min-history по умолчанию равно 0, вы обычно не увидите, что ref-history-count возвращает ненулевые значения, если только не возникает разногласий по ссылке.

См. дополнительные обсуждения history count здесь: https://groups.google.com/forum/?fromgroups#!topic/clojure/n_MKCoa870o

Я не думаю, что есть какой-либо способ, предоставленный clojure.core, наблюдать за скоростью транзакций STM в данный момент. Конечно, вы можете сделать что-то похожее на то, что сделал @Chouser в своем историческом стресс-тесте:

(dosync
    (swap! try-count inc)
    ...)

то есть увеличить счетчик внутри транзакции. Приращение будет происходить каждый раз при попытке транзакции. Если try-count больше 1, транзакция повторяется.

person liwp    schedule 18.06.2013
comment
Никогда не думал об использовании счетчика истории или атомов в txs. Спасибо за предложения! - person deprecated; 19.06.2013

Введя именованные блоки досинхронизации и счетчики коммитов (количество успешных именованных досинхронизаций), можно довольно легко отслеживать количество повторных попыток выполнения данной транзакции потоками.

(def ^{:doc "ThreadLocal<Map<TxName, Map<CommitNumber, TriesCount>>>"}
  local-tries (let [l (ThreadLocal.)]
                (.set l {})
                l))

(def ^{:doc "Map<TxName, Int>"}
  commit-number (ref {}))

(def history ^{:doc "Map<ThreadId, Map<TxName, Map<CommitNumber, TriesCount>>>"}
  (atom {}))

(defn report [_ thread-id tries]
  (swap! history assoc thread-id tries))

(def reporter (agent nil))

(defmacro dosync [tx-name & body]
  `(clojure.core/dosync
    (let [cno# (@commit-number ~tx-name 0)
          tries# (update-in (.get local-tries) [~tx-name] update-in [cno#] (fnil inc 0))]
      (.set local-tries tries#)
      (send reporter report (.getId (Thread/currentThread)) tries#))
    ~@body
    (alter commit-number update-in [~tx-name] (fnil inc 0))))

Учитывая следующий пример...

(def foo (ref {}))

(def bar (ref {}))

(defn x []
  (dosync :x ;; `:x`: the tx-name.
          (let [r (rand-int 2)]
            (alter foo assoc r (rand))
            (Thread/sleep (rand-int 400))
            (alter bar assoc (rand-int 2) (@foo r)))))

(dotimes [i 4]
  (future
   (dotimes [i 10]
     (x))))

...@history оценивается как:

;; {thread-id {tx-name {commit-number tries-count}}}
{40 {:x {3 1, 2 4, 1 3, 0 1}}, 39 {:x {2 1, 1 3, 0 1}}, ...}
person deprecated    schedule 19.06.2013

Эта дополнительная реализация существенно проще.

;; {thread-id retries-of-latest-tx}
(def tries (atom {}))

;; The max amount of tries any thread has performed
(def max-tries (atom 0))

(def ninc (fnil inc 0))

(def reporter (agent nil))

(defn report [_ tid]
  (swap! max-tries #(max % (get @tries tid 0)))
  (swap! tries update-in [tid] (constantly 0)))

(defmacro dosync [& body]
  `(clojure.core/dosync
    (swap! tries update-in [(.getId (Thread/currentThread))] ninc)
    (commute commit-id inc)
    (send reporter report (.getId (Thread/currentThread)))
    ~@body))
person deprecated    schedule 24.06.2013