clojure 的 atom 和 ref 小探秘

  • 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 会怎样呢?待续。。

转载于:https://my.oschina.net/soitravel/blog/1560868

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值