<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    莊周夢蝶

    生活、程序、未來
       :: 首頁 ::  ::  :: 聚合  :: 管理

    Clojure的并發(一) Ref和STM

    Posted on 2010-07-14 02:34 dennis 閱讀(7869) 評論(8)  編輯  收藏 所屬分類: 動態語言javaClojure

    Clojure 的并發(一) Ref和STM
    Clojure 的并發(二)Write Skew分析
    Clojure 的并發(三)Atom、緩存和性能
    Clojure 的并發(四)Agent深入分析和Actor
    Clojure 的并發(五)binding和let
    Clojure的并發(六)Agent可以改進的地方
    Clojure的并發(七)pmap、pvalues和pcalls
    Clojure的并發(八)future、promise和線程

        Clojure處理并發的思路與眾不同,采用的是所謂STM的模型——軟事務內存。你可以將STM想象成數據庫,只不過是內存型的,它只支持事務的ACI,也就是原子性、一致性、隔離性,但是不包括持久性,因為狀態的保存都在內存里。

        Clojure的并發API分為四種模型:
    1、管理協作式、同步修改可變狀態的Ref
    2、管理非協作式、同步修改可變狀態的Atom
    3、管理異步修改可變狀態的Agent
    4、管理Thread local變量的Var。

        下面將對這四部分作更詳細的介紹。

    一、Ref和STM

     1、ref:

    通過ref函數創建一個可變的引用(reference),指向一個不可變的對象:
    (ref x)

    例子:創建一個歌曲集合:
    (def song (ref #{}))

    2、deref和@:
     取引用的內容,解引用使用deref函數
    (deref song)

    也可以用reader宏@:
    @song

    3、ref-set和dosync:


    改變引用指向的內容,使用ref-set函數
    (ref-set ref new-value)

    如,我們設置新的歌曲集合,加入一首歌:
    (ref-set song #{"Dangerous"})
    但是這樣會報錯:
    java.lang.IllegalStateException: No transaction running (NO_SOURCE_FILE:0)

    這是因為引用是可變的,對狀態的更新需要進行保護,傳統語言的話可能采用鎖,Clojure是采用事務,將更新包裝到事務里,這是通過dosync實現的:
    (dosync (ref-set song #{"Dangerous"}))

    dosync的參數接受多個表達式,這些表達式將被包裝在一個事務里,事務支持ACI:
    (1)Atomic,如果你在事務里更新多個Ref,那么這些更新對事務外部來說是一個獨立的操作。
    (2)Consistent,Ref的更新可以設置 validator,如果某個驗證失敗,整個事務將回滾。
    (3)Isolated,運行中的事務無法看到其他事務部分完成的結果。

    dosync更新多個Ref,假設我們還有個演唱者Ref,同時更新歌曲集合和演唱者集合:
    (def singer (ref #{}))
    (dosync (ref
    -set song #{"Dangerous"})
                   (ref
    -set singer #{"MJ"}) )

    @song      
    =>  #{"Dangerous"}
    @singer    
    =>  #{"MJ"}

    4、alter:
    完全更新整個引用的值還是比較少見,更常見的更新是根據當前狀態更新,例如我們向歌曲集合添加一個歌曲,步驟大概是先查詢集合內容,然后往集合里添加歌曲,然后更新整個集合:
    (dosync (ref-set song (conj @song "heal the world")))

    查詢并更新的操作可以合成一步,這是通過alter函數:
    (alter ref update-fn & args)

    alter接收一個更新的函數,函數將在更新的時候調用,傳入當前狀態值并返回新的狀態值,因此上面的例子可以改寫為:
     (dosync (alter song conj "heal the world"))

    這里使用conj而非cons是因為conj接收的第一個參數是集合,也就是當前狀態值,而cons要求第一個參數是將要加入的元素。

    5、commute:
      commute函數是alter的變形,commute顧名思義就是要求update-function是可交換的,它的順序是可以任意排序。commute的允許的并發程度比alter更高一些,因此性能會更好。但是由于commute要求update-function是可交換的,并且會自動重排序,因此如果你的更新要求順序性,那么commute是不能接受的,commute僅可用在對順序性沒有要求或者要求很低的場景:例如更新聊天窗口的聊天信息,由于網絡延遲的因素和個人介入的因素,聊天信息可以認為是天然排序,因此使用commute還可以接受,更新亂序的可能性很低。
      另一個例子就不能使用commute了,如實現一個計數器:
    (def counter (ref 0))

      實現一個next-counter函數獲取計數器的下一個值,我們先使用commute實現:
    (defn next-counter [] (dosync (commute counter inc)))

       這個函數很簡單,每次調用inc遞增counter的值,接下來寫個測試用例:啟動50個線程并發去獲取next counter:
    (dotimes [_ 50] (.start (Thread. #(println (next-counter)))))
      
       這段代碼稍微解釋下,dotimes是重復執行50次,每次啟動new并啟動一個Thread,這個Thread里干了兩件事情:調用next-counter,打印調用結果,第一個版本的next-counter執行下,這是其中一次輸出的截取:
    23
    23
    23

    23
    23
    23
    23
    23
    23
    23
    23
    23
    28
    23
    21
    23
    23
    23
    23
    25
    28

    可以看到有很多的重復數值,這是由于重排序導致事務結束后的值不同,但是你查看counter,確實是50:
    @counter  => 50

    證明更新是沒有問題的,問題出在commute的返回值上。

    如果將next-counter修改為alter實現:
    (defn next-counter [] (dosync (alter counter inc)))

    此時再執行測試用例,可以發現打印結果完全正確了:
    ……
    39
    41
    42
    45
    27
    46
    47
    44
    48
    43
    49
    40
    50

    查看counter,也是正確更新到50了:
    @counter => 50

    最佳實踐:通常情況下,你應該優先使用alter,除非在遇到明顯的性能瓶頸并且對順序不是那么關心的時候,可以考慮用commute替換。

    6、validator:
       類似數據庫,你也可以為Ref添加“約束”,在數據更新的時候需要通過validator函數的驗證,如果驗證不通過,整個事務將回滾。添加validator是通過ref函數傳入metadata的map實現的,例如我們要求歌曲集合添加的歌曲名稱不能為空:
    (def validate-song
         (partial every? #(not (nil?
    %))))

    (def song (ref #{} :validator validate
    -song))

    validate-song是一個驗證函數,partial返回某個函數的半函數(固定了部分參數,部分參數沒固定),你可以將partial理解成currying,雖然還是不同的。validate-song調用every?來驗證集合內的所有元素都不是nil,其中#(not (nil? %))是一個匿名函數,%指向匿名函數的第一個參數,也就是集合的每個元素。ref指定了validator為validate-song,那么在每次更新song集合的時候都會將新的狀態傳入validator函數里驗證一下,如果返回false,整個事務將回滾:

    (dosync (alter song conj nil))
    java.lang.IllegalStateException: Invalid reference state (NO_SOURCE_FILE:
    0)

    更新失敗,非法的reference狀態,查看song果然還是空的:
    @song => #{}

    更新正常的值就沒有問題:
     (dosync (alter song conj "dangerous"))   => #{"dangerous"}

       
    7、ensure:

      ensure函數是為了保護Ref不會被其他事務所修改,它的主要目的是為了防止所謂的“寫偏序”(write skew)問題。寫偏序問題的產生跟STM的實現有關,clojure的STM實現是基于MVCC(Multiversion Concurrency Control)——多版本并發控制,對一個Ref保存多個版本的狀態值,在更新的時候取得當前狀態值的一個隔離的snapshot,更新是基于snapshot進行的。那么我們來看下寫偏序是怎么產生,以一個比喻來描述:
      想象有一個系統用于管理美國最神秘的軍事禁區——51區的安全巡邏,你有3個營的士兵,每個營45個士兵,并且你需要保證總體巡邏的士兵人數不能少于100個人。假設有一天,有兩個指揮官都登錄了這個管理系統,他們都想從某個軍營里抽走20個士兵,假設指揮官A想從1號軍營抽走,指揮官B想要從2號軍營抽走士兵,他們同時執行下列操作:
    Admin 1if ((G1 - 20+ G2 + G3) > 100 then dispatchPatrol

    Admin 
    2if (G1 + (G2 - 20+ G3) > 100 then dispatchPatrol

    我們剛才提到,Clojure的更新是基于隔離的snapshot,一個事務的更改無法看到另一個事務更改了部分的結果,因此這兩個操作都因為滿足(45-20)+45+45=115的約束而得到執行,導致實際抽調走了40個士兵,只剩下95個士兵,低于設定的安全標準100人,這就是寫偏序現象。
      寫偏序的解決就很簡單,在執行抽調前加入ensure即可保護ref不被其他事務所修改。ensure比(ref-set ref @ref)允許的并發程度更高一些。


    Ref和STM的介紹暫時到這里,原理和源碼的解析要留待下一篇文章了。




    評論

    # re: Clojure的并發(一) Ref和STM  回復  更多評論   

    2010-07-14 19:09 by clojans
    樓主高手呀!
    但4個模型前要加個"管理"形容詞呢?不是只有ref才是協調管理的嗎?

    # re: Clojure的并發(一) Ref和STM  回復  更多評論   

    2010-07-14 19:10 by clojans
    樓主高手呀!
    但4個模型前為什么要加個"管理"形容詞呢?不是只有ref才是協調管理的嗎?

    --1 Lou寫錯了:-(

    # re: Clojure的并發(一) Ref和STM  回復  更多評論   

    2010-07-14 19:12 by nmb
    樓主晚上不睡覺嗎?

    # re: Clojure的并發(一) Ref和STM  回復  更多評論   

    2010-07-14 20:30 by dennis
    @clojans
    非形容詞,而是動詞

    # re: Clojure的并發(一) Ref和STM  回復  更多評論   

    2011-08-19 13:42 by sw2wolf
    不錯! 加深我對HASKELL相關概念的理解

    # re: Clojure的并發(一) Ref和STM  回復  更多評論   

    2013-09-25 13:14 by paomian
    莊莊我愛你!

    # re: Clojure的并發(一) Ref和STM  回復  更多評論   

    2014-03-26 13:11 by flyfoxs
    commute 會導致執行修改ref的函數執行2次. 這樣設計的目的是為什么,難道這2次執行又一次不是多余的嗎?

    # re: Clojure的并發(一) Ref和STM[未登錄]  回復  更多評論   

    2014-07-04 18:11 by sure
    commute 重排序的到底是什么?
    主站蜘蛛池模板: 女人18毛片水真多免费播放| 免费又黄又爽又猛的毛片| 亚洲天堂2016| 亚洲国产精品自在拍在线播放 | 无码人妻一区二区三区免费视频 | 亚洲变态另类一区二区三区| 亚洲美女在线国产| 日本最新免费网站| 黄色毛片免费在线观看| 亚洲色欲www综合网| 亚洲精品成人久久久| AV无码免费永久在线观看| 男女交性无遮挡免费视频| 亚洲精品日韩中文字幕久久久| 免费人成网站在线高清| 日韩免费的视频在线观看香蕉| 亚洲人成在线播放| 国产美女无遮挡免费视频网站| 国产精品福利片免费看 | 久久久久无码专区亚洲av | 日韩在线永久免费播放| 亚洲香蕉久久一区二区| 亚洲成AⅤ人影院在线观看 | 免费va在线观看| 久久免费国产视频| 亚洲日韩一区二区一无码| 久久久久亚洲AV成人网| 国产一卡二卡四卡免费| 老司机午夜性生免费福利| 免费少妇a级毛片| 久久国产精品萌白酱免费| 亚洲人成人77777在线播放| 日韩视频免费一区二区三区| 白白色免费在线视频| 久久久久亚洲AV无码观看| 亚洲精品成人久久久| 毛片a级毛片免费播放下载| caoporn成人免费公开| 亚洲熟伦熟女专区hd高清| 国产国拍亚洲精品mv在线观看 | 日韩精品电影一区亚洲|