Posted on 2010-07-30 21:35
dennis 閱讀(3251)
評論(0) 編輯 收藏 所屬分類:
Clojure
Clojure
的并發(一) Ref和STM
Clojure
的并發(二)Write Skew分析
Clojure
的并發(三)Atom、緩存和性能
Clojure
的并發(四)Agent深入分析和Actor
Clojure
的并發(五)binding和let
Clojure的并發(六)Agent可以改進的地方
Clojure的并發(七)pmap、pvalues和pcalls
Clojure的并發(八)future、promise和線程
前面幾節基本介紹Clojure對并發的支持,估計這個系列還能寫個兩三節,介紹下一些常見的concurrent function的使用。談了很多優點,現在想談談clojure的一些缺憾,例如Agent系統。
Agent在前面已經介紹過了,用于異步更新,基本的原理是將更新操作封裝為action,提交給線程池處理。內部有兩個線程池,固定大小(cpus+2)的線程池用于處理send發送的action,而send-off發送的action則是由一個Cached ThreadPool處理的。因此,如果你的更新操作很耗時或者會阻塞(如IO操作),那么通常是建議使用send-off,而send適合一些計算密集型的更新操作。兩個線程池的聲明如下(Agent.java):
1 final public static ExecutorService pooledExecutor =
2 Executors.newFixedThreadPool(2 + Runtime.getRuntime().availableProcessors());
3
4 final public static ExecutorService soloExecutor = Executors.newCachedThreadPool();
說說我認為Agent可以改進的地方。
首先是從實踐角度出發,通常我們要求new一個線程池的時候,要設置線程池內線程的name,以方便查看和調試。因此Clojure這里簡單的new線程池是一個可以改進的地方,應當自定義一個ThreadFactory,給clojure的Agent線程提供明確的名稱。
其次,由于這兩個線程池是全局的,因此clojure提供了shutdown-agents的方法用于關閉線程池。但是由于這些線程池內的線程并非daemon,因此如果你沒有明確地調用shutdown-agents,jvm也可以正常退出。
我們都知道,如果還有dadmon線程沒有終止,JVM是無法退出的。如果JVM只剩下daemon線程,那么jvm就會自動退出。從實踐角度,應當明確地要求用戶調用shutdown-agents來關閉Agent系統,妥善終止線程,并且Agent的線程池應當延遲初始化,只在必要的時候創建,而非現在的靜態變量。所以,在實現ThreadFactory的時候,應當設置生成的線程為daemon。
第三,同樣由于線程池是全局的,關閉了卻沒有辦法重新啟動,這不能不說是一個缺憾。Clojure沒有提供重新啟動的方法。
第四,線程池簡單地分為兩類,從理論上足以滿足大部分應用的要求。但是在real world的應用上,我們通常不敢用CachedThreadPool,這是為了防止內存不受控,導致線程創建過多直接OOM。通常我們會使用固定大小的線程池,但是clojure固定大小的線程池只有一個,并且大小寫死為cpus+2,這就沒有了控制的余地。我還是希望clojure能提供允許自定義Agent線程池的方法,可以在創建的時候傳入線程池,如:
(agent :executor (java.util.concurrent.Executors/newFixedThreadPool 2))
或者提供新的API,如set-executor!來設置agent使用的線程池,如果沒有自定義線程池再使用全局的。當然也需要提供一個關閉agent自定義線程池的API:
(shutdown-agent agent)
需要自定義線程池是另一個原因是為了最大化地發揮線程池的效率,我們知道,線程池只有在執行“同構”任務的時候才能發揮最大的效率,如果有的 action快,有的action慢,那么該快的快不起來,慢的卻擠占了快的action的執行時間。通過給Agent設定自己的線程池某些程度上可以解決這個問題。
Agent的整個模型是很優雅的,但是確實還有這些地方不是特別讓人滿意,希望以后會得到改進。