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

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

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

    莊周夢蝶

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

    Clojure的并發(五)binding和let

    Posted on 2010-07-23 23:19 dennis 閱讀(4989) 評論(1)  編輯  收藏 所屬分類: Clojure
    Clojure 的并發(一) Ref和STM
    Clojure 的并發(二)Write Skew分析
    Clojure 的并發(三)Atom、緩存和性能
    Clojure 的并發(四)Agent深入分析和Actor
    Clojure 的并發(五)binding和let
    Clojure的并發(六)Agent可以改進的地方
    Clojure的并發(七)pmap、pvalues和pcalls
    Clojure的并發(八)future、promise和線程

    五、binding和let

        前面幾節已經介紹了Ref、Atom和Agent,其中Ref用于同步協調多個狀態變量,Atom只能用于同步獨立的狀態變量,而Agent則是允許異步的狀態更新。這里將介紹下binding,用于線程內的狀態的管理。

    1、binding和let:
    當你使用def定義一個var,并傳遞一個初始值給它,這個初始值就稱為這個var的root binding。這個root binding可以被所有線程共享,例如:
    user=> (def foo 1)
    #
    'user/foo
        那么對于變量foo來說,1是它的root binding,這個值對于所有線程可見,REPL的主線程可見:
    user=> foo
    1
       啟動一個獨立線程查看下foo的值:
    user=> (.start (Thread. #(println foo)))
    nil
     
    1
      可以看到,1這個值對于所有線程都是可見的。
     
      但是,利用binding宏可以給var創建一個thread-local級別的binding:
    (binding [bindings] & body)

      binding的范圍是動態的,binding只對于持有它的線程是可見的,直到線程執行超過binding的范圍為止,binding對于其他線程是不可見的。
    user=> (binding [foo 2] foo)
    2

      粗看起來,binding和let非常相似,兩者的調用方式近乎一致:
    user=> (let [foo 2] foo)
    2

      從一個例子可以看出兩者的不同,定義一個print-foo函數,用于打印foo變量:
    user=> (defn print-foo [] (println foo))
    #
    'user/print-foo

      foo不是從參數傳入的,而是直接從當前context尋找的,因此foo需要預先定義。分別通過let和binding來調用print-foo:
    user=> (let [foo 2] (print-foo))
    1
    nil

      可以看到,print-foo仍然打印的是初始值1,而不是let綁定的2。如果用binding:
    user=> (binding [foo 2] (print-foo))
    2
    nil

       print-foo這時候打印的就是binding綁定的2。這是為什么呢?這是由于let的綁定是靜態的,它并不是改變變量foo的值,而是用一個詞法作用域的foo“遮蔽”了外部的foo的值。但是print-foo卻是查找變量foo的值,因此let的綁定對它來說是沒有意義的,嘗試利用set!去修改let的foo:
    user=> (let [foo 2] (set! foo 3))
    java.lang.IllegalArgumentException: Invalid assignment target (NO_SOURCE_FILE:
    12)
      
       Clojure告訴你,let中的foo不是一個有效的賦值目標,foo是不可變的值。set!可以修改binding的變量:
    user=> (binding [foo 2] (set! foo 3) (print-foo))
    3
    nil

    2、Binding的妙用:


    Binding可以用于實現類似AOP編程這樣的效果,例如我們有個fib函數用于計算階乘:
    user=> (defn fib [n]
             (loop [ n n r 
    1]
                (
    if (= n 1)
                    r
                    (recur (dec n) (
    * n r)))))

    然后有個call-fibs函數調用fib函數計算兩個數的階乘之和:
    user=> (defn call-fibs [a b]
              (
    + (fib a) (fib b)))
    #
    'user/call-fibs
    user=> (call-fibs 3 3)
    12

      現在我們有這么個需求,希望使用memoize來加速fib函數,我們不希望修改fib函數,因為這個函數可能其他地方用到,其他地方不需要加速,而我們希望僅僅在調用call-fibs的時候加速下fib的執行,這時候可以利用binding來動態綁定新的fib函數:
    user=> (binding [fib (memoize fib)] 
                    (call
    -fibs 9 10))
    3991680

       在沒有改變fib定義的情況下,只是執行call-fibs的時候動態改變了原fib函數的行為,這不是跟AOP很相似嗎?

       但是這樣做已經讓call-fibs這個函數不再是一個“純函數”,所謂“純函數”是指一個函數對于相同的參數輸入永遠返回相同的結果,但是由于binding可以動態隱式地改變函數的行為,導致相同的參數可能返回不同的結果,例如這里可以將fib綁定為一個返回平方值的函數,那么call-fibs對于相同的參數輸入產生的值就改變了,取決于當前的context,這其實是引入了副作用。因此對于binding的這種使用方式要相當慎重。這其實有點類似Ruby中的open class做monkey patch,你可以隨時隨地地改變對象的行為,但是你要承擔相應的后果。

    3、binding和let的實現上的區別


    前面已經提到,let其實是詞法作用域的對變量的“遮蔽”,它并非重新綁定變量值,而binding則是在變量的root binding之外在線程的ThreadLocal內存儲了一個綁定值,變量值的查找順序是先查看ThreadLocal有沒有值,有的話優先返回,沒有則返回root binding。下面將從Clojure源碼角度分析。

    變量在clojure是存儲為Var對象,它的內部包括:

    //這是變量的ThreadLocal值存儲的地方
    static ThreadLocal<Frame> dvals = new ThreadLocal<Frame>(){

        
    protected Frame initialValue(){
            
    return new Frame();
        }
    };

    volatile Object root;  //這是root binding
    public final Symbol sym;   //變量的符號
    public final Namespace ns;  //變量的namespace

    通過def定義一個變量,相當于生成一個Var對象,并將root設置為初始值。

    先看下let表達式生成的字節碼:
    (let [foo 3] foo)
    字節碼:
    public class user$eval__4349 extends clojure/lang/AFunction  {

      
    // compiled from: NO_SOURCE_FILE
      
    // debug info: SMAP
    eval__4349.java
    Clojure
    *S Clojure
    *F
    + 1 NO_SOURCE_FILE
    NO_SOURCE_PATH
    *L
    0#1,1:0
    *E

      
    // access flags 25
      public final static Ljava/lang/Object; const__0

      
    // access flags 9
      public static <clinit>()V
       L0
        LINENUMBER 
    2 L0
        ICONST_3
        INVOKESTATIC java
    /lang/Integer.valueOf (I)Ljava/lang/Integer;
        PUTSTATIC user$eval__4349.const__0 : Ljava
    /lang/Object;

        RETURN
        MAXSTACK 
    = 0
        MAXLOCALS 
    = 0

      
    // access flags 1
      public <init>()V
       L0
        LINENUMBER 
    2 L0
       L1
        ALOAD 
    0
        INVOKESPECIAL clojure
    /lang/AFunction.<init> ()V
       L2
        RETURN
        MAXSTACK 
    = 0
        MAXLOCALS 
    = 0

      
    // access flags 1
      public invoke()Ljava/lang/Object; throws java/lang/Exception 
       L0
        LINENUMBER 
    2 L0
        GETSTATIC user$eval__4349.const__0 : Ljava
    /lang/Object;
        ASTORE 
    1
       L1
        ALOAD 
    1
       L2
        LOCALVARIABLE foo Ljava
    /lang/Object; L1 L2 1
       L3
        LOCALVARIABLE 
    this Ljava/lang/Object; L0 L3 0
        ARETURN
        MAXSTACK 
    = 0
        MAXLOCALS 
    = 0
    }

        可以看到foo并沒有形成一個Var對象,而僅僅是將3存儲為靜態變量,最后返回foo的時候,也只是取出靜態變量,直接返回,沒有涉及到變量的查找。let在編譯的時候,將binding作為編譯的context靜態地編譯body的字節碼,body中用到的foo編譯的時候就確定了,沒有任何動態性可言。

        再看同樣的表達式替換成binding宏,因為binding只能重新綁定已有的變量,所以需要先定義foo:
    user=> (def foo 100)
    #
    'user/foo
    user=> (binding [foo 3] foo)

        binding是一個宏,展開之后等價于:
    (let []
             (push
    -thread-bindings (hash-map (var foo) 3))
             (
    try
                foo
             (
    finally
                (pop
    -thread-bindings))))

        首先是將binding的綁定列表轉化為一個hash-map,其中key為變量foo,值為3。函數push-thread-bindings:

    (defn push-thread-bindings
         [bindings]
         (clojure.lang.Var
    /pushThreadBindings bindings))
       
        其實是調用Var.pushThreadBindings這個靜態方法:
    public static void pushThreadBindings(Associative bindings){
        Frame f 
    = dvals.get();
        Associative bmap 
    = f.bindings;
        
    for(ISeq bs = bindings.seq(); bs != null; bs = bs.next())
            {
            IMapEntry e 
    = (IMapEntry) bs.first();
            Var v 
    = (Var) e.key();
            v.validate(v.getValidator(), e.val());
            v.count.incrementAndGet();
            bmap 
    = bmap.assoc(v, new Box(e.val()));
            }
        dvals.set(new Frame(bindings, bmap, f));
    }

        pushThreadBindings是將綁定關系放入一個新的frame(新的context),并存入ThreadLocal變量dvals。pop-thread-bindings函數相反,彈出一個Frame,它實際調用的是Var.popThreadBindings靜態方法:
    public static void popThreadBindings(){
        Frame f 
    = dvals.get();
        
    if(f.prev == null)
            
    throw new IllegalStateException("Pop without matching push");
        
    for(ISeq bs = RT.keys(f.frameBindings); bs != null; bs = bs.next())
            {
            Var v 
    = (Var) bs.first();
            v.count.decrementAndGet();
            }
        dvals.set(f.prev);
    }

       在執行宏的body表達式,也就是取foo值的時候,實際調用的是Var.deref靜態方法取變量值:
    final public Object deref(){
        
    //先從ThreadLocal找
        Box b = getThreadBinding();
        
    if(b != null)
            
    return b.val;
        
    //如果有定義初始值,返回root binding
        if(hasRoot())
            
    return root;
        
    throw new IllegalStateException(String.format("Var %s/%s is unbound.", ns, sym));
    }

        看到是先嘗試從ThreadLocal找:
    final Box getThreadBinding(){
        
    if(count.get() > 0)
            {
            IMapEntry e 
    = dvals.get().bindings.entryAt(this);
            
    if(e != null)
                
    return (Box) e.val();
            }
        
    return null;
    }

       找不到,如果有初始值就返回初始的root binding,否則拋出異常:Var user/foo is unbound.
       binding表達式最后生成的字節碼,做的就是上面描述的這些函數調用,有興趣地可以自行分析。

       

    評論

    # re: Clojure的并發(五)binding和let  回復  更多評論   

    2010-07-26 09:43 by clojans
    樓主應該加上let和binding表達式返回函數的用途和區別就更加完美了。
    主站蜘蛛池模板: 亚洲成av人片在线看片| 亚洲国产成人久久综合| 24小时日本在线www免费的| 亚洲AV香蕉一区区二区三区| 亚洲一级片免费看| 久久99国产综合精品免费| 亚洲国产成人精品无码区二本 | 成在线人免费无码高潮喷水| 色播亚洲视频在线观看| 免费观看美女裸体网站| 免费无码又爽又刺激一高潮| 亚洲高清一区二区三区| 日韩精品亚洲aⅴ在线影院| 国产卡二卡三卡四卡免费网址| 特级做a爰片毛片免费看| 亚洲日本在线观看网址| 亚洲午夜精品一级在线播放放| ww在线观视频免费观看| 国产三级在线免费观看| 亚洲综合欧美色五月俺也去| 亚洲成AV人片在线观看| 免费在线不卡视频| 中文字幕无码免费久久99| 在线观看免费黄网站| 老牛精品亚洲成av人片| 亚洲国产精品综合福利专区| 国产亚洲美女精品久久久2020| 大香人蕉免费视频75| 曰批全过程免费视频播放网站| 国产人成网在线播放VA免费| 亚洲欧洲国产综合AV无码久久| 亚洲视频在线观看免费视频| 自拍偷自拍亚洲精品情侣| 国产成人无码免费视频97| 一本无码人妻在中文字幕免费| 暖暖免费日本在线中文| 久久毛片免费看一区二区三区| 国产亚洲精品成人久久网站| 亚洲精品第一综合99久久| 亚洲色图综合网站| 亚洲三级电影网站|