- swap! 函数是不是原子操作?
(def counter (atom 1))
(defn swap-long-sleep-inc
[atom]
(swap! atom #(do (print "start ")
(Thread/sleep 100)
(print "end ")
(inc %))))
(defn swap-inc
[atom]
(swap! atom #(do (print (str @atom " "))
(Thread/sleep 10)
(inc %))))
(doall (pcalls #(swap-long-sleep-inc counter)
#(while (< (swap-inc counter) 20))))
(println \newline "counter=" @counter)
;;start 1 2 3 4 5 6 7 8 9 10 end start 11 12 13 14 15 16 17 18 19 end start end
;;counter= 21
swap-long-sleep-inc 和 swap-inc 函数都是对 atom 类型的变量 at 进行加一操作,前者持续了 100ms,并在开始和结束时候打印出“start"和”end“,而后者较为快速地执行,每次打印出 at 的值。
现用两个线程,一个执行 swap-long-sleep-inc 函数,另一个线程执行 swap-inc 函数20次,如果 swap! 函数是原子操作 (atomic),那打印出的”start"和“end"之间就不应该穿插第二个线程打印的内容。
而执行结果并不是这样。有意思的是”start"和"end"出现多次,说明 swap! 函数不是原子操作,而是像 alter 函数对 ref 的操作那样,如果在执行过程中 atom 的值被更新,就用更新的值重新执行
[ 注意:如果在多线程中用 (println ...) 打印多个参数,不同线程之间会交错打印导致混乱,最好是用 (print (str ... \newline)),用 str 先对多个参数求值后在一次打印 ]
- alter 是在 dosync 结尾出才检查更新吗?
(def counter (ref 1))
(defn alter-long-sleep-inc
[ref]
(dosync (print "start-dosync ")
(alter ref #(do (print "start-alter ")
(Thread/sleep 100)
(print "end-alter ")
(inc %)))
(print "end-dosync ")))
(defn alter-inc
[ref]
(dosync (alter ref #(do (Thread/sleep 10)
(print (str @ref " "))
(inc %)))))
(doall (pcalls #(alter-long-sleep-inc counter)
#(while(< (alter-inc counter) 10))))
(println \newline "counter=" @counter)
;;start-dosync start-alter 1 2 3 4 5 6 7 8 9 end-alter start-dosync start-alter end-alter end-dosync
;;counter= 11
这是把第一个例子中 swap! 操作 atom 改成 alter 操作 ref。
第一个线程在开始和结束 dosync 和 alter 函数时都会打印出标记,而从打印结果看来,执行完 alter 函数后发现 ref 已被修改,即使没有执行完所有 dosync 内的表达式("end-dosync“ 还没打印出来),也会回到 dosync 开头重新执行。
可以进一步试验,让 alter-long-sleep-inc 函数中修改两个 ref 的值,会发现只要其中某个 alter 函数执行完发现其参数 ref 值已经更新过,整个 dosync 就会重新执行
- commute 就好玩(奇怪)了
(def counter (ref 1))
(defn commute-long-sleep-inc
[ref]
(dosync (print "start-dosync ")
(commute ref #(do (print "start-commute ")
(Thread/sleep 100)
(print "end-commute ")
(inc %)))
(print "end-dosync ")))
(defn commute-inc
[ref]
(dosync (commute ref #(do (Thread/sleep 20)
(print (str @ref " "))
(inc %)))))
(doall (pcalls #(commute-long-sleep-inc counter)
#(while(< (commute-inc counter) 7))))
(println \newline "counter=" @counter)
;;start-dosync start-commute 1 1 2 2 end-commute end-dosync start-commute 3 end-commute 4 5 5 6 6
;;counter= 7
文档说即使 commute 执行时 ref 的值更新了, dosync 也不会重新执行,而是在最后提交时用更新的值重新执行一次 commute。从打印的结果看来确实是在 dosync 结束后 commute 又执行了一次。
但第二次执行的 commute 是不是原子操作呢?如果不是,又怎么保证线程安全呢?怎么保证这次执行过程中 ref 值不会再更新呢?从打印结果看第二次执行 commute 过程中穿插者其他线程打印的值(” ... start-commute 3 end-commute ...“之一段)。
如果把第二次 commute 执行过程称为"正式提交",我的理解是: 不能有两个线程同时对同一个 ref "正式提交"。所以第一个线程 commute “正式提交”的打印结果中穿插的那个 ”3“ 是第二个线程 “非正式提交” 打印出来的,后面那个 “4” 才是第二个线程“正式提交”的 commute 过程中打印出来的。这么理解对吗?
- 如果一段 dosync 中执行了 alter 和 commute 会怎样呢?待续。。