《七週七併發模型》是我在書店尋找Actor相關資料時偶遇的一本200多頁的小冊子。目錄看似簡單,實際的內容卻涵蓋了多種編程語言、併發模型與框架,對我實在是一大考驗。斷斷續續地看了一個多月才勉強讀完一遍,很粗淺地對書中提到的模型有了大體的瞭解。因爲其中每一個模型都須要大量的基礎知識支撐,很容易讓本身陷入碎片化學習的泥沼,所以暫時我就把它當個入門的索引了。java
並行加速率=(Ws+Wp)/(Ws+Wp/p)
受計算中並行份量Wp、串行份量Ws、處理器數量p影響,加速化曲線呈S狀梯形上升。算法
如前所述,因爲涉及的語言和框架的跨度較大,每一個章節又相對獨立、知識點相對零散,因此我沒法從中窺探出一幅真正意義上的路線圖,只能記下其中的聊聊數筆。編程
以哲學家進餐爲例,誰同時拿上左右兩支筷子才能吃上飯,其餘人則只能等待他放下筷子才能機會拿齊一雙筷子進餐。這就是線程與鎖的故事——簡單而粗暴。這一把把筷子就比如是一把把的鎖,這鍋飯就比如你們競爭的共享數據。沒拿到筷子的人,即被阻塞在不停搶筷子的循環裏——天知道我何時能搶到?安全
因爲存在多把鎖的同時競爭,很容易形成死鎖。因而引入了一個簡單而有效的規則:『始終按照一個全局的、固定的順序獲取多把鎖』。好比說約定老是先取左手邊的筷子,再取右手邊的。因而獲得以下的一個示意圖。圖中的每一個元素、每一個環節都須要進行同步化。數據結構
儘管問題獲得初步解決,但內置鎖在線程被阻塞後沒法中斷,形成線程假死,並且鎖越多意味着死鎖的風險也越大。因而引入可中斷的鎖、超時鎖、條件變量等一些彌補機制。其中的條件變量condition機制,在Java 5.0和.NET 4.0以後的併發框架中獲得了普遍應用。在引入Condition後,獲得哲學家進餐問題的第二個版本:再也不是每支筷子對應一把鎖,而改成視整張餐桌爲一把鎖,再把左右哲學家進餐結束視做競爭條件。這樣,當左右兩邊的哲學家進餐結束時,就意味着本身能夠進餐了。架構
private void eat() { // 先獲取鎖 table.lock(); try { // 若是條件知足就解鎖並await一直等待 while (left.eating || right.eating) condition.await(); // 接收到其餘線程經過signal()或signalAll()發出的信號 // 因而條件得以知足,加鎖後訪問共享資源 eating = true; } finally { table.unlock(); } } private void think() { table.lock(); try { eating = false; left.condition.signal(); right.condition.signal(); } finally { table.unlock(); } }
拆解問題,能夠得出統計須要三個步驟:用下載的XML文本構造出若干張Page,而後逐頁分析每張Page裏出現的單詞Word,最後合計每一個Word出現的次數。併發
在一般的串行化方案(逐行解析並統計)以後,依次給出了以下三個併發解決方案:框架
我對函數式編程FP的認識,就是y=f(x)
,對函數f給定x就必定獲得y,不會由於f或者x持有其餘的狀態而產生不一樣於y的其餘結果。而在FP的世界,除了常量與遞歸成爲常態外,最多見的就是map、reduce、fold等一些映射與聚合函數了。我只想說,FP的實現代碼真是簡潔得可怕!異步
(defn get-words [text] (re-seq #"\w+" text)) (defn count-words-sequential [pages] (frequencies (mapcat get-words pages)))
(defn count-words-parallel [pages] (reduce (partial merge-with +) (pmap #(frequencies (get-words %)) pages)))
(defn count-words [pages] (reduce (partial merge-with +) (pmap count-words-sequential (partition-all 100 pages))))
(defn parallel-frequencies [coll] (r/fold (partial merge-with +) (fn [counts x] (assoc counts x (inc (get counts x 0)))) coll))
這部分多數涉及Clojure的框架,因此我只關注了與FP密切相關的『持久數據結構』。async
理解持久數據結構,有點相似於理解『引用』與引用指向的『內存塊』。而在FP裏,純粹的函數是不會修改既有結構的,由於它老是產生一個新的結果。
(def list_1 (1, 2, 3)) (def list_2 (cons 4 list_1)) (def list_3 (cons 5 (rest list_1)))
這段代碼將產生下面這樣的一個鏈表結構:
由此展現了CP與FP的一大重要區別:在CP中,一個變量既是標識Identity也是狀態State,你在此時拿到某個列表是是(1,2,3),下一刻可能被別人改成(1,3,4,5),而在FP中則會始終是(1,2,3),這即是持久數據結構的本質。即一個標識,會對應多個版本、隨時間變化的值。
「使用Actor就像租車——若是咱們須要,能夠快速便捷地租到一輛;若是車輛發生故障,也不須要本身修理,直接打電話給租車公司更換一輛便可。」
每一個Actor,都是一個封閉的、有狀態的、自帶郵箱、經過消息與外界進行協做的併發實體。在Actors之間的消息發送、接收是併發的,可是在Actor內部,消息被郵箱存儲後都是串行處理的。即Actor在同一時刻只會對一條 異步消息作出迴應,從而回避鎖策略。
使用Actor編程有個很不一樣尋常的編程思想——「任其崩潰」!這是由於每一個Actor都被其監督者管理,這些不一樣層次的Actor及其監督者搭建成一棵完整的Actor模型樹。這棵樹的葉結點是各類Actor,非葉的結點則是監督者。當某個Actor出現錯誤而崩潰時,由其監督者採起重啓、忽略錯誤、記錄緣由等措施。
消化掉以上兩點,我就開始讀《Reactive Messaging Patterns with the Actor Model》做爲進階了。最後,一樣援引Smalltalk設計者、面向對象之父Alan Kay的一段話結束本節。好吧,我認可誤入歧途了。
好久之前,我在描述「面向對象編程」時使用了「對象」這個概念。很抱歉,這個概念讓許多人誤入歧途,他們將學習重心放在了「對象」這個次要的方面。真正主要的方面是「消息」……建立一個規模宏大且能夠生長的系統,關鍵在於其模塊之間應如何交流,而不在於其內部的屬性,以及行爲應該如何表現。
CSP(Communicating Sequential Process,通訊順序進程),和Actor比較相似。CSP不關心消息是誰發送的,只關心用於消息傳遞的那個通道Channel,我把這個Channel視做一個線程安全的消息隊列,消息兩端與消息隊列自己是脫耦的。而在這方面,Actor模型中的消息兩端是明確已知的,消息隊列也是由Actor郵箱自帶的。
CSP的執行體主要是各類Go塊。這個Go塊就當是一個狀態機,這與C#中async和await的實現是一致的,具體參考《CLR via C#》第4版第649頁『28.3 編譯器如何將異步函數轉換爲狀態機』。
這部分主要圍繞矩陣、向量等線性代數方面所需的大量數值計算,引入OpenCL驅動GPU進行並行計算。這方面我沒什麼研究,直接跳過了。
這章我只知道Map-Reduce是Lambda的主要基石,將問題分解爲Map和Reduce兩個部分是一切的關鍵。其中,Map負責把輸入映射爲若干對key-value,而後由Reduce負責聚合這些key-value,輸出最終數據。
除了Map-Reduce,爲了解決報表與分析等一些須要及時反饋的信息,又引入了流處理技術。個人理解,就是對原始數據進行一個合理的分片,再利用批處理生成一個與報表需求一致的中間結果批處理視圖,最後再借由服務層按需拼湊成最終結果。這個部分,合理的分片和拼湊算法是關鍵。
若是及時響應的要求還要更高,那麼還有個加速層的東西,根據最後一次生成批處理視圖的原始數據,直接生成相應的派生信息。這個部分,決定哪些數據過時、如何讓其過時是關鍵。
分佈式的世界,分佈式的軟件。