• 并发
    • Future
    • Delay
    • Promise
    • Atom
    • Watch
    • Validator
    • Ref
    • Dynamic var

    并发

    Clojure是一门支持并发编程的语言,它提供了很多特性让我们非常的方便进行并发程序的开发。

    Future

    Future可以让我们在另一个线程去执行任务,它是异步执行的,所以调用了future之后会立即返回。譬如:

    1. user=> (future (Thread/sleep 3000) (println "I'am back"))
    2. #object[clojure.core$future_call$reify__6736 0x7118d905 {:status :pending, :val nil}]
    3. user=> (println "I'am here")
    4. I'am here
    5. nil
    6. user=> I'am back

    在上面的例子中,sleep会阻塞当前线程的执行,但是因为我们用了future,所以clojure将其放到了另一个线程中,然后继续执行下面的语句。

    有时候,我们使用了future之后,还需要知道future的任务执行的结果,知识后就需要用defer来实现了。我们可以使用defer或者@来获取futureresult,譬如:

    1. user=> (let [result (future (println "run only once") (+ 1 1))]
    2. #_=> (println (deref result))
    3. #_=> (println @result))
    4. run only once
    5. 2
    6. 2
    7. nil

    deref还可以支持timeout设置,如果超过了等待时间,就返回一个默认值。

    1. user=> (deref (future (Thread/sleep 100) 0) 10 5)
    2. 5
    3. user=> (deref (future (Thread/sleep 100) 0) 1000 5)
    4. 0

    我们也可以使用realized?来判断一个future是否完成

    1. user=> (realized? (future (Thread/sleep 1000)))
    2. false
    3. user=> (let [f (future)]
    4. #_=> @f
    5. #_=> (realized? f))
    6. true

    Delay

    Delay可以让我们定义一个稍后执行的任务,并不需要现在立刻执行。

    1. user=> (def my-delay
    2. #_=> (delay (let [msg "hello world"]
    3. #_=> (println msg)
    4. #_=> msg)))
    5. #'user/my-delay

    我们可以通过@或者force来执行delay的任务

    1. user=> @my-delay
    2. hello world
    3. "hello world"
    4. user=> (force my-delay)
    5. "hello world"

    Clojure会将delay的任务结果缓存,所以第二次delay的调用我们直接获取的是缓存结果。

    我们可以将delayfuture一起使用,定义一个delay操作,在future完成之后,调用delay,譬如:

    1. user=> (let [notify (delay (println "hello world"))]
    2. #_=> (future ((Thread/sleep 1000) (force notify))))
    3. #object[clojure.core$future_call$reify__6736 0x2de625f3 {:status :pending, :val nil}]
    4. user=> hello world

    Promise

    Promise是一个承诺,我们定义了这个promise,就预期后续会得到相应的result。我们通过deliver来将result发送给对应的promise,如下:

    1. user=> (def my-promise (promise))
    2. #'user/my-promise
    3. user=> (deliver my-promise (+ 1 1))
    4. #object[clojure.core$promise$reify__6779 0x30dc687a {:status :ready, :val 2}]
    5. user=> @my-promise
    6. 2

    promise也跟delay一样,会缓存deliverresult

    1. user=> (deliver my-promise (+ 1 2))
    2. nil
    3. user=> @my-promise
    4. 2

    我们也可以将promisefuture一起使用

    1. user=> (let [hello-promise (promise)]
    2. #_=> (future (println "Hello" @hello-promise))
    3. #_=> (Thread/sleep 1000)
    4. #_=> (deliver hello-promise "world"))
    5. Hello world

    Atom

    在并发编程里面,a = a + 1这条语句并不是安全的,在clojure里面,我们可以使用atom完成一些原子操作。如果大家熟悉c语言里面的compare and swap那一套原子操作函数,其实对Clojureatom也不会陌生了。

    我们使用atom创建一个atom,然后使用@来获取这个atom当前引用的值:

    1. user=> (def n (atom 1))
    2. #'user/n
    3. user=> @n
    4. 1

    如果我们需要更新该atom引用的值,我们需要通过一些原子操作来完成,譬如:

    1. user=> (swap! n inc)
    2. 2
    3. user=> @n
    4. 2
    5. user=> (reset! n 0)
    6. 0
    7. user=> @n
    8. 0

    Watch

    对于一些数据的状态变化,我们可以使用watch来监控,一个watch function包括4个参数,关注的key,需要watchreference,譬如atom等,以及该reference之前的state以及新的state,譬如:

    1. user=> (defn watch-n
    2. #_=> [key watched old-state new-state]
    3. #_=> (if (> new-state 1)
    4. #_=> (println "new" new-state)
    5. #_=> (println "old" old-state)))
    6. user=> (def wn (atom 1))
    7. #'user/wn
    8. user=> @wn
    9. 1
    10. user=> (add-watch wn :a watch-n)
    11. #object[clojure.lang.Atom 0x4b28dcf8 {:status :ready, :val 1}]
    12. user=> (reset! wn 1)
    13. old 1
    14. 1
    15. user=> (reset! wn 2)
    16. new 2
    17. 2

    Validator

    我们可以使用validator来验证某个reference状态改变是不是合法的,譬如:

    1. user=> (defn validate-n
    2. #_=> [n]
    3. #_=> (> n 1))
    4. #'user/validate-n
    5. user=> (def vn (atom 2 :validator validate-n))
    6. #'user/vn
    7. user=> (reset! vn 10)
    8. 10
    9. user=> (reset! vn 0)
    10. IllegalStateException Invalid reference state clojure.lang.ARef.validate (ARef.java:33)

    在上面的例子里面,我们建立了一个validator,参数n必须大于1,否则就是非法的。

    Ref

    在前面,我们知道使用atom,能够原子操作某一个reference,但是如果要操作一批atom,就不行了,这时候我们就要使用ref

    1. user=> (def x (ref 0))
    2. #'user/x
    3. user=> (def y (ref 0))
    4. #'user/y

    我们创建了两个ref对象xy,然后在dosync里面对其原子更新

    1. user=> (dosync
    2. #_=> (ref-set x 1)
    3. #_=> (ref-set y 2)
    4. #_=> )
    5. 2
    6. user=> [@x @y]
    7. [1 2]
    8. user=> (dosync
    9. #_=> (alter x inc)
    10. #_=> (alter y inc))
    11. user=> (dosync
    12. #_=> (commute x inc)
    13. #_=> (commute y inc))

    ref-set就类似于atom里面的reset!alter就类似swap!,我们可以看到,还有一个commute也能进行reference的更新,它类似于alter,但是稍微有一点不一样。

    在一个事务开始之后,如果使用alter,那么在修改这个reference的时候,alter会去看有没有新的修改,如果有,就会重试当前事务,而commute则没有这样的检查。所以如果通常为了性能考量,并且我们知道程序没有并发的副作用,那就使用commute,如果有,就老老实实使用alter了。

    我们通过@来获取reference当前的值,在一个事务里面,我们通过ensure来保证获取的值一定是最新的:

    1. user=> (dosync
    2. #_=> (ref-set x (ensure y)))

    Dynamic var

    通常我们通过def定义一个变量之后,最好就不要更改这个var了,但是有时候,我们又需要在某些context里面去更新这个var的值,这时候,最好就使用dynamic var了。

    我们通过如下方式定义一个dynamic var:

    1. user=> (def ^:dynamic *my-var* "hello")
    2. #'user/*my-var*

    dynamic var必须用*包裹,在lisp里面,这个叫做earmuffs,然后我们就能够通过binding来动态改变这个var了:

    1. user=> (binding [*my-var* "world"] *my-var*)
    2. "world"
    3. user=> (println *my-var*)
    4. hello
    5. user=> (binding [*my-var* "world"] (println *my-var*)
    6. #_=> (binding [*my-var* "clojure"] (println *my-var*)) (println *my-var*))
    7. world
    8. clojure
    9. world

    可以看到,binding只会影响当前的stack binding