詳解為什么現(xiàn)代系統(tǒng)需要一個(gè)新的編程模型
為什么現(xiàn)代系統(tǒng)需要一個(gè)新的編程模型?
Actor模型作為一種高性能網(wǎng)絡(luò)中的并行處理方式由Carl Hewitt幾十年前提出-高性能網(wǎng)絡(luò)環(huán)境在當(dāng)時(shí)還不可用。如今,硬件和基礎(chǔ)設(shè)施的能力已經(jīng)趕上并超越了Hewitt的愿景。因此,高要求的分布式系統(tǒng)的建造者遇到了不能完全由傳統(tǒng)的面向?qū)ο缶幊?OOP)模型解決的挑戰(zhàn),但這可以從Actor模型中獲益。
今天,Actor模型不僅被認(rèn)為是高效的解決方案——這已經(jīng)被世界上要求最高的應(yīng)用所檢驗(yàn)。為了突出Actor模型解決的問題,這個(gè)主題討論以下傳統(tǒng)編程的假設(shè)與現(xiàn)代多線程、多CPU體系架構(gòu)之間的不匹配:
- 封裝的挑戰(zhàn)
- 現(xiàn)代計(jì)算機(jī)體系結(jié)構(gòu)中共享內(nèi)存的錯(cuò)覺
- 一個(gè)調(diào)用棧的錯(cuò)覺
封裝的挑戰(zhàn)
OOP的一個(gè)核心支柱是封裝。封裝表明一個(gè)對(duì)象的內(nèi)部狀態(tài)不能直接從外部訪問;它只可以通過調(diào)用一組輔助的方法修改。對(duì)象負(fù)責(zé)暴露保護(hù)它所封裝數(shù)據(jù)的不變性的安全操作。例如,在一個(gè)有序二叉樹上的操作不允許違反樹的有序性。調(diào)用者希望保持有序性,當(dāng)查詢樹上一條特定的數(shù)據(jù)時(shí),它們需要能夠依賴這個(gè)約束。
當(dāng)分析OOP運(yùn)行時(shí)的行為時(shí),我們有時(shí)候畫出一個(gè)消息序列圖展示方法調(diào)用的交互過程。例如:
不幸的是,上面的圖表沒能精確表示執(zhí)行過程中對(duì)象的生命線。實(shí)際上,一個(gè)線程執(zhí)行所有的調(diào)用,所有對(duì)象的不變體約束出現(xiàn)在同一個(gè)方法被調(diào)用的線程中。更新線程執(zhí)行圖,它看起來是這樣:
當(dāng)試圖對(duì)多線程行為建模時(shí),上面闡述的重要性變得明顯了。突然,我們畫出的簡(jiǎn)潔的圖表變得不夠充分了。我們可以嘗試解釋多線程訪問同一對(duì)象:
有一個(gè)執(zhí)行部分,兩個(gè)線程調(diào)用同一個(gè)方法。不幸的是,對(duì)象的封裝模型不能保證執(zhí)行這部分時(shí)會(huì)發(fā)生什么。兩個(gè)線程之間沒有某種協(xié)調(diào)的話,兩個(gè)調(diào)用指令將以不能保證不變體性質(zhì)的任意方式相互交錯(cuò)?,F(xiàn)在,想象一下這個(gè)由多個(gè)線程存在而變得復(fù)雜的問題。
解決這個(gè)問題的常見方法是給這些方法加一個(gè)鎖。盡管這保證了在給定的時(shí)間內(nèi)最多一個(gè)線程將執(zhí)行該方法,但是這是一個(gè)代價(jià)高昂的策略:
- 鎖嚴(yán)重限制了并發(fā),鎖在現(xiàn)代CPU體系結(jié)構(gòu)中的代價(jià)很高,要求操作系統(tǒng)承擔(dān)掛起線程并隨后恢復(fù)它的重負(fù)。
- 調(diào)用者線程被阻塞,因此它不能做其他有意義的工作。在桌面應(yīng)用中這是不能接受的,我們希望使應(yīng)用程序的用戶界面(UI)即使在一個(gè)很長(zhǎng)的后臺(tái)作業(yè)正在運(yùn)行的時(shí)候也是可響應(yīng)的。在后臺(tái),阻塞是完全浪費(fèi)的。或許有人想到這可以通過開啟一個(gè)新線程彌補(bǔ),但線程也是一個(gè)代價(jià)高昂的抽象。
- 鎖引入了一個(gè)新的威脅:死鎖
這些事實(shí)導(dǎo)致一個(gè)無法取勝的局面:
- 沒有足夠的鎖,狀態(tài)會(huì)被破壞
- 有足夠的鎖,性能受損并很容易導(dǎo)致死鎖
另外,鎖只有在本地有用。當(dāng)涉及跨機(jī)器協(xié)調(diào)時(shí),唯一可選的是分布式鎖。不幸的是,分布式鎖比本地鎖低效幾個(gè)數(shù)量級(jí),并且限制了伸縮性。分布式鎖協(xié)議需要在網(wǎng)絡(luò)中跨機(jī)器的多輪通信,因此延遲飛漲。
在面向?qū)ο笳Z言中,我們通常很少考慮線路或線性執(zhí)行路徑。我們經(jīng)常把系統(tǒng)想象成一個(gè)對(duì)象實(shí)例的網(wǎng)絡(luò),這些實(shí)例對(duì)象響應(yīng)方法調(diào)用、修改自身內(nèi)部狀態(tài)、然后通過方法調(diào)用相互通信以驅(qū)動(dòng)整個(gè)應(yīng)用狀態(tài)向前:
然而,在一個(gè)多線程的分布式環(huán)境中,實(shí)際發(fā)生的是線程沿著方法調(diào)用貫穿這個(gè)對(duì)象實(shí)例網(wǎng)絡(luò)。因此,線程是真正的運(yùn)行驅(qū)動(dòng)者:
【總結(jié)】
- 對(duì)象只能在單線程訪問時(shí)保證封裝(不變體的保護(hù)),多線程執(zhí)行幾乎總會(huì)導(dǎo)致破壞對(duì)象內(nèi)部狀態(tài)。每個(gè)不變體可以被處于同一代碼段相互競(jìng)爭(zhēng)的兩個(gè)線程違反。
- 雖然鎖似乎是對(duì)維護(hù)多線程時(shí)的封裝很自然的補(bǔ)救,實(shí)際上,在任何現(xiàn)實(shí)應(yīng)用中鎖很低效并很容易導(dǎo)致死鎖。
- 鎖在本地有用,但試圖使鎖成為分布式的,可以提供有限潛力的擴(kuò)展。
現(xiàn)代計(jì)算機(jī)體系結(jié)構(gòu)中共享內(nèi)存的錯(cuò)覺
80-90年代的編程模型定義:寫入一個(gè)變量意味著直接寫到內(nèi)存位置 (這在一定程上混淆了局部變量可能僅存在于寄存器)。在現(xiàn)代體系架構(gòu)中,如果我們簡(jiǎn)化一下,CPUs會(huì)寫到cache行而不是直接寫入內(nèi)存。大多數(shù)caches是CPU局部私有的,也就是,一個(gè)核寫入變量不會(huì)被其他核看到。為了使局部改變對(duì)其他核可見,因此對(duì)于另一個(gè)線程,cache行需要被傳送到其他核的cache。
在JVM中,我們必須通過使用volatile或Atomic顯式地指示線程間共享的內(nèi)存位置。否則,我們只能在鎖定的部分訪問這些內(nèi)存。為什么我們不將所有變量標(biāo)記為volatile?因?yàn)榭绾藗魉蚦ache行是一個(gè)代價(jià)非常高昂的操作!這樣做會(huì)隱式地停止涉及做額外工作的核,并導(dǎo)致緩存一致性協(xié)議的瓶頸。(CPUs用于主存和其他CPUs之間傳輸cache行的協(xié)議)。結(jié)果便是降低數(shù)量級(jí)的運(yùn)行速度。
即使對(duì)于了解這個(gè)情況的開發(fā)者,搞清楚哪個(gè)內(nèi)存位置應(yīng)該被標(biāo)記為volatile或者使用哪一種原子結(jié)構(gòu)是一門黑暗的藝術(shù)。
【總結(jié)】
- 沒有真正的共享內(nèi)存了,CPU核就像網(wǎng)絡(luò)中的計(jì)算機(jī)一樣,將數(shù)據(jù)塊(cache行)顯式地傳送給彼此。CPU之間的通信和網(wǎng)絡(luò)中計(jì)算機(jī)之間通信的相同之處比許多人意識(shí)到的要多。傳送消息是如今跨CPUs或網(wǎng)絡(luò)中計(jì)算機(jī)的標(biāo)準(zhǔn)。
- 相對(duì)于通過標(biāo)記為共享或使用原子數(shù)據(jù)結(jié)構(gòu)的變量來隱藏消息傳遞的層面,一個(gè)更規(guī)范和有原則的方法是保存狀態(tài)到一個(gè)并發(fā)實(shí)體本地并通過消息顯式地在并發(fā)實(shí)體間傳送數(shù)據(jù)或事件。
一個(gè)調(diào)用棧的錯(cuò)覺
今天,我們常常將調(diào)用棧視為理所當(dāng)然。但是,調(diào)用棧是在一個(gè)并發(fā)程序不那么重要的時(shí)代發(fā)明的,因?yàn)槎郈PU系統(tǒng)那時(shí)不常見。調(diào)用棧沒有跨越線程,因而沒有對(duì)異步調(diào)用鏈建模。
當(dāng)一個(gè)線程意圖委派一個(gè)任務(wù)給后臺(tái)的時(shí)候會(huì)出現(xiàn)問題。實(shí)際上,這意味著委托給另一個(gè)線程。這不是一個(gè)簡(jiǎn)單的方法、函數(shù)調(diào)用,因?yàn)檎{(diào)用嚴(yán)格上屬于線程內(nèi)部。通常,調(diào)用者(caller)線程將一個(gè)對(duì)象放入與一個(gè)工作線程(callee)共享的內(nèi)存位置,反過來,這個(gè)工作線程(callee)在某個(gè)循環(huán)事件中獲取這個(gè)對(duì)象。這使得調(diào)用者(caller)線程可以向前運(yùn)行和執(zhí)行其他任務(wù)。
第一個(gè)問題是:調(diào)用者(caller)線程如何被通知任務(wù)完成了?但是當(dāng)一個(gè)任務(wù)失敗且?guī)в挟惓5臅r(shí)候一個(gè)更嚴(yán)重問題出現(xiàn)了。異常應(yīng)該傳播到哪里?異常將被傳播到工作者(worker)線程的異常處理器而完全忽略誰是真正的調(diào)用者(caller):
這是一個(gè)嚴(yán)重的問題。工作者(worker)線程如何處理這種情況?它可能無法解決這個(gè)問題,因?yàn)樗ǔ2恢朗∪蝿?wù)的目的。調(diào)用者(caller)線程需要以某種方式被通知,但是沒有調(diào)用棧去返回一個(gè)異常。失敗通知只能通過邊信道完成,例如,將一個(gè)錯(cuò)誤代碼放在調(diào)用者(caller)線程原本期待結(jié)果準(zhǔn)備好的地方。如果這個(gè)通知不到位,調(diào)用者(caller)線程不會(huì)被通知任務(wù)失敗和丟失!這和網(wǎng)絡(luò)系統(tǒng)的工作方式驚人地相似-網(wǎng)絡(luò)系統(tǒng)中的消息和請(qǐng)求可以丟失或失敗而沒有任何通知。
在任務(wù)出錯(cuò)和一個(gè)工作者(worker)線程遇到一個(gè)bug并不可恢復(fù)的時(shí)候,這個(gè)糟糕的情況會(huì)變得更糟。例如,一個(gè)由bug引起的內(nèi)部異常向上傳遞到工作者(worker)線程的根部并使該線程關(guān)閉。這立即產(chǎn)生一個(gè)疑問,誰應(yīng)該重啟由該線程持有的這一服務(wù)的正常操作,以及怎樣將它恢復(fù)到一個(gè)已知的良好狀態(tài)?乍一看,這似乎很容易,但是我們突然遇到一個(gè)新的、意外的現(xiàn)象:線程正在執(zhí)行的實(shí)際任務(wù)已經(jīng)不在任務(wù)被取走得共享內(nèi)存位置了 (通常是一個(gè)隊(duì)列)。事實(shí)上,由于異常到達(dá)頂部,展開所有的調(diào)用棧,任務(wù)狀態(tài)完全丟失了!我們已經(jīng)丟失了一條消息,盡管這是本地的通信,沒有涉及到網(wǎng)絡(luò) (消息丟失是可期望的)。
【總結(jié)】
為了在當(dāng)下系統(tǒng)實(shí)現(xiàn)有意義的并發(fā)和性能,線程必須以一種高效的、無阻塞的方式相互委派任務(wù)。有了這種任務(wù)委派并發(fā)方式(網(wǎng)絡(luò)/分布式計(jì)算更是如此),基于棧調(diào)用的error處理失效了,新的、顯式的error信號(hào)機(jī)制需要被引入。失敗成為領(lǐng)域模型的一部分。任務(wù)委派的并發(fā)系統(tǒng)需要處理服務(wù)故障并且有原則性的方法恢復(fù)它們。這種服務(wù)的客戶端需要知道任務(wù)/消息會(huì)在重啟中丟失。即使不丟失,一個(gè)響應(yīng)或許會(huì)由于隊(duì)列 (一個(gè)很長(zhǎng)的隊(duì)列) 中先前的任務(wù)而發(fā)生任意的延遲,由垃圾回收造成的延遲等等。在這些情況下,并發(fā)系統(tǒng)應(yīng)該以超時(shí)的形式對(duì)待響應(yīng)截止時(shí)間,就像網(wǎng)絡(luò)/分布式系統(tǒng)一樣。
以上就是詳解為什么現(xiàn)代系統(tǒng)需要一個(gè)新的編程模型的詳細(xì)內(nèi)容,更多關(guān)于現(xiàn)代系統(tǒng)需要一個(gè)新的編程模型的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺談架構(gòu)模式變遷之從分層架構(gòu)到微服務(wù)架構(gòu)
一般地,架構(gòu)模式大致可以分成兩類,單體架構(gòu)(monolithic architecture)和分布式架構(gòu)(distributed architecture)。2021-05-05chrome開發(fā)者助手插件v2.10發(fā)布提升開發(fā)效率不再只是口號(hào)
這篇文章主要介紹了chrome開發(fā)者助手插件v2.10發(fā)布提升開發(fā)效率不再只是口號(hào),這個(gè)版本重點(diǎn)提升了常用工具的使用效率,需要的朋友可以參考下2021-03-03Scala函數(shù)式編程專題--函數(shù)思想介紹
這篇文章主要介紹了Scala函數(shù)式編程的的相關(guān)資料,文中講解非常細(xì)致,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-06-06一文讀懂modbus slave和modbus poll使用說明
modbus poll和modbus slave是一款實(shí)用的modbus開發(fā)和調(diào)試工具,可以非常方便的進(jìn)行modbus調(diào)試,是非常有用的Modbus主機(jī)/從機(jī)模擬程序,這篇文章給大家介紹modbus slave和modbus poll使用說明,感興趣的朋友一起看看吧2021-04-04淺談測(cè)試驅(qū)動(dòng)開發(fā)TDD之爭(zhēng)
在軟件行業(yè)中,神仙打架的名場(chǎng)面,那就不得不提的是2014年的那場(chǎng)——測(cè)試驅(qū)動(dòng)開發(fā)(TDD)之爭(zhēng)。2021-05-05詳解Python OpenCV數(shù)字識(shí)別案例
信用卡識(shí)別的案例用到了圖像處理的一些基本操作,對(duì)剛上手CV的人來說還是比較友好的。2021-05-05