Akka中最重要的即是actor模型。html
幾十年前,Carl Hewitt提出了actor模型,以做爲在高性能網絡下並行處理的一種方式。可是在當時並無這樣的環境,現在硬件和基礎設施能力已經遇上並超越了Carl Hewitt當時的預料。因此,那些想構建高性能分佈式系統的組織遇到的使用面向對象編程(OOP)模型沒法徹底解決的挑戰,如今可使用actor模型解決。編程
現在,actor模型不只被認爲是一種高效的解決方案,並且也已經在世界上一些要求苛刻的應用中獲得了驗證。爲了突出actor模型所能解決的問題,本主題主要討論傳統編程思想與現代多線程多CPU架構之間的不匹配:後端
OOP的核心是封裝。封裝規定對象內的數據不能直接從外部訪問,只能調用方法來進行修改。對象須要暴露出一些安全的操做,這些安全操做用來維護其內部數據的約束。緩存
例如,對有序二叉樹的操做不得違反二叉樹有序的約束。調用者但願排序是完整的,當查詢樹中某個數據時,他們須要可以依賴這個約束。安全
當咱們分析OOP運行時行爲時,咱們有時會繪製一個時序圖,顯示方法調用的交互。微信
不幸的是,上圖並不能準確地表示執行期間實例的生命週期。實際上,全部這些調用發生在同一線程上。網絡
當您嘗試模擬多線程狀況時,上面的這種表達方式就變得更加清晰了。由於咱們能夠經過下圖來表示兩個線程訪問同一個實例:多線程
兩個線程進入同一個對象相同的方法,可是對象的封裝模型並不能很好的表達這其中發生的事情。兩個線程能夠以任意方式交錯,想象一下,若是是多線程,這個問題會更加嚴重。架構
解決此問題的經常使用方法是給這些方法加鎖。雖然這確保了在任何給定時間最多隻有一個線程將進入該方法,但這是一種很是昂貴的策略:併發
這些現實致使了一種尷尬的局面:
此外,鎖只能在本地很好地工做。在協調跨多臺機器時,惟一的選擇是分佈式鎖。不幸的是,分佈式鎖的效率比本地鎖效率要差幾個級別,而且在擴展時有更多的限制。分佈式鎖須要在多臺計算機上經過網絡進行屢次通訊往返,所以還存在延遲。
在面嚮對象語言中,咱們不多考慮線程的執行路徑。咱們常常將系統設想爲一個由對象組織成的網絡,它們對方法調用做出反應,並修改其內部狀態,而後經過方法調用相互通訊,從而驅動整個應用程序運行。
可是,在多線程分佈式環境中,其實是線程經過方法調用「遍歷」此對象網絡。所以,真正推進應用程序運行的是線程:
總結:
在80-90年代的編程概念模型中,本地變量是直接寫入到內存中的(這和咱們理解的本地變量是存在寄存器中是不同的)。在現代架構上,CPU是寫入到緩存行而不是直接寫入內存的。這些高速緩存大多數都在CPU內核中,也就是說,一個內核的寫入不會被另外一個內核看到。爲了使內核中的本地更改對另外一個核心可見,須要將緩存行傳送到另外一個核心中。
在JVM中,咱們必須使用volatile標記或使用Atomic包裝類明確表示變量要跨線程進行內存共享。不然,咱們只能先加鎖而後訪問它們。爲何咱們不將全部變量都標記爲volatile?由於跨核心同步緩存行是一項很是昂貴的操做!這樣作會阻止內核去執行額外的工做,並致使緩存一致性協議出現瓶頸。
即便對於瞭解這種狀況的開發人員來講,肯定哪些變量應該被標記爲volatile,或者使用哪一種atomic結構也是一種藝術。
總結:
今天,咱們將調用棧視爲理所固然。可是,它們是在一個併發編程並不重要的時代發明的,由於那時多CPU系統並不常見,調用棧不會跨線程。
當主線程打算將任務委託給「後臺」時,這實際上就是將任務委託給另一個工做線程,實際上就是主線程將一個任務對象放入工做線程中的一個共享隊列裏,工做線程負責從這個隊列裏獲取任務來執行,這就容許主線程繼續前進並執行其餘任務。
他的第一個問題是,工做線程如何通知主線程任務已完成?當任務因異常而失敗時會出現更嚴重的問題,異常傳播到哪裏?真實狀況是它將傳播到工做線程的異常處理程序,而後徹底忽略了實際的「調用者」是主線程:
這是一個嚴重的問題。主線程的調用棧上不能捕獲這個異常,工做線程如何處理這種狀況?須要以某種方式通知主線程,例如將異常放在主線程預先準備存放結果的地方,但若是主線程一直沒有收到通知,任務也即丟失!
當工做線程在執行任務時出現BUG,致使工做線程關閉,這時誰來從新啓動一個線程來處理這個任務,而且該任務如何恢復到正常的狀態。這都是問題。
總結:
原文爲akka官網連接:doc.akka.io/docs/akka/c…
接下來的文章,讓咱們看看actor模型如何克服這些挑戰。
若是以爲這篇文章能讓你學到知識,可否幫忙轉發,將知識分享出去。 若是想第一時間學習更多的精彩的內容,請關注微信公衆號:1點25