这个参考设置有什么问题?

时间:2016-03-17 02:28:09

标签: clojure

我有以下代码:

 (ns fwpd.core
 (:import java.util.concurrent.Executors))

 (def thread-pool
  (Executors/newFixedThreadPool
  (+ 2 (.availableProcessors (Runtime/getRuntime)))))

 (defn dothreads!
    [f & {thread-count :threads
            exec-count :times
          :or {thread-count 1 exec-count 1}}]
    (dotimes [t thread-count]
       (.submit thread-pool
         #(dotimes [_ exec-count] (f)))))

 (def all-users (ref {}))

 (defn new-user [id login monthly-budget]
   {:id id
    :login login
    :monthly-budget monthly-budget
    :total-expense 0})

 (defn add-new-user [login monthly-budget]
 (dosync
    (let [current-user (count @all-users)
          user (new-user (inc current-user) login monthly-budget)]
    (alter all-users assoc login user))))

当我在REPL中加载它并使用以下命令运行时:

(dothreads! #(add-new-user (str (rand-int 500) "name") 5000) :threads 4 :times 4)

我看到我有时会获得具有相同ID的用户,尽管这些名称是随机生成的,并且不会像我预期的那样发生冲突。

我到底错过了什么?

2 个答案:

答案 0 :(得分:0)

据我所知,无法保证@all-users中的值与all-users调用中(alter all-users ...)的值一致,即使在dosync事务中也是如此。您应该在alter语句中移动整个count-and-create算法。

此外,由于您只处理单个可变实体,因此您可能应该atom使用swap!

答案 1 :(得分:0)

问题在于您使用(rand-int 500)生成的名称。 您看到的行为没有任何并发​​性,这是将ID分配给现有用户的方式中的缺陷。 考虑一下:

(add-new-user "a" 100)
(add-new-user "a" 100)
(add-new-user "b" 100)
(prn @all-users)

=> {"a" {:id 2, :login "a", :monthly-budget 100, :total-expense 0},
    "b" {:id 2, :login "b", :monthly-budget 100, :total-expense 0}}

这里发生了什么?我们创建了用户" a"当所有用户都没有,所以" a"获得id 1.然后我们创建了另一个用户" a"它将获得id 2,但是当你添加新的" a"在地图上,它取代了旧的" a" ...所以现在有一个" a" ID为2.现在我们添加" b",在所有用户中有1个用户,因此它将获得id 2 ...与" a" !!!

当您使用(rand-int 500)时,您可能会收到相同的号码。您可以使用(rand-int 5)或50或500000查看效果。为什么不使用ID作为密钥呢?

(defn add-new-user [login monthly-budget]
  (dosync
    (let [current-user (count @all-users)
          id (inc current-user)
          user (new-user id login monthly-budget)]
      (alter all-users assoc id user))))

(dotimes [i 10]
  (prn "START")
  (dothreads! #(add-new-user (str (rand-int 10) "name") 5000) :threads 4 :times 4)
  (Thread/sleep 1000)
  (prn "COUNT" (count @all-users))
  (if (apply distinct? (map :id (vals @all-users)))
    (prn "Distinct ids")
    (prn "*** NOT distinct ***" (sort (map :id (vals @all-users))))))

=>
"START"
"COUNT" 16
"Distinct ids"
"START"
"COUNT" 32
"Distinct ids"
...

另请注意,通常名称不一定是唯一的,但ID确实如此。因此,当你有一个id时,使用name作为键是一个提示是错误的。