[轉(zhuǎn)自程序員]
深遠(yuǎn)影響
在過(guò)去30年里,并發(fā)雖然一直被鼓吹為“下一件大事”或“未來(lái)之路”,但軟件界不為所動(dòng)。現(xiàn)在,并行終于出現(xiàn)在我們面前了:新一代計(jì)算機(jī)全面支持并發(fā),這將引發(fā)軟件開(kāi)發(fā)方式的巨變。
本文主要討論并發(fā)對(duì)軟件——包括編程語(yǔ)言和程序員——的深遠(yuǎn)影響。
Olukotun和Hammond所描述的硬件發(fā)展,代表著計(jì)算機(jī)計(jì)算方式的重大變化。過(guò)去30年里,半導(dǎo)體工業(yè)的發(fā)展及其在處理器上的應(yīng)用,推動(dòng)了已有順序式軟件運(yùn)行速度的穩(wěn)步提升。但體系結(jié)構(gòu)上的多核處理器變化僅對(duì)并發(fā)應(yīng)用有益,因此幾乎對(duì)絕大多數(shù)現(xiàn)存軟件沒(méi)有價(jià)值。在不久的將來(lái),已有的桌面應(yīng)用不可能比現(xiàn)在跑得更快。實(shí)際上,它們的運(yùn)行速度會(huì)稍慢,因?yàn)闉榱私档透呙芏榷嗪颂幚砥鞯碾娔芟模碌?a >芯片內(nèi)核被簡(jiǎn)化并運(yùn)行在更低的時(shí)鐘速度。
這將對(duì)軟件至少是主流軟件的開(kāi)發(fā)帶來(lái)深遠(yuǎn)影響。計(jì)算機(jī)的能力無(wú)疑越來(lái)越強(qiáng),但程序不再可能從硬件性能提升大餐中免費(fèi)獲益,除非它們實(shí)現(xiàn)了并發(fā)。
即便拋開(kāi)多核變化的強(qiáng)制要求不談,我們也有理由實(shí)現(xiàn)并發(fā),尤其是將工作從同步轉(zhuǎn)到異步,可以提高響應(yīng)速度,就像目前的應(yīng)用里必須讓工作遠(yuǎn)離GUI線程,以便計(jì)算在后臺(tái)進(jìn)行時(shí),屏幕能得到重繪。
但實(shí)現(xiàn)并發(fā)是有難度的。不僅目前的語(yǔ)言和工具仍未為將應(yīng)用轉(zhuǎn)化為并行程序做好充分準(zhǔn)備,而且在主流應(yīng)用中也很難找到并行,尤其糟糕的是——并發(fā)要求程序員以人類(lèi)難以適應(yīng)的方式思維。
不過(guò),多核在未來(lái)不可回避,我們必須找到與之相適應(yīng)的軟件開(kāi)發(fā)方式。接下來(lái),我們將深入探討并發(fā)的難度所在,以及一些未來(lái)可能的應(yīng)對(duì)方向。
軟件新紀(jì)元
今天的并發(fā)編程語(yǔ)言和工具幾乎與結(jié)構(gòu)化編程時(shí)代初期的順序化編程同時(shí)起步。信號(hào)量和協(xié)程是實(shí)現(xiàn)并發(fā)的基本手段,鎖與線程建立在更高層次,可以結(jié)構(gòu)化構(gòu)造并發(fā)程序。而我們需要的是以面向?qū)ο鬄榛A(chǔ)的并發(fā)——建立在更高層次的抽象有助于構(gòu)建并發(fā)程序,就像面向?qū)ο蟪橄笥幸嬗跇?gòu)建大型組件化程序一樣。
幾個(gè)因素決定了并發(fā)變革對(duì)我們沖擊可能比面向?qū)ο蟠蟆J紫龋l(fā)是獲得更高性能的必需手段。像C之類(lèi)的語(yǔ)言,無(wú)視面向?qū)ο螅阅茉诤芏嚅_(kāi)發(fā)領(lǐng)域發(fā)揮作用。如果并發(fā)變成了應(yīng)用性能提升的華山一條路,那么商業(yè)和系統(tǒng)語(yǔ)言的存在價(jià)值就只能建立在支持并發(fā)編程的基礎(chǔ)上。因此現(xiàn)存的如C之類(lèi)的語(yǔ)言,就必須支持超越pthreads之流簡(jiǎn)單模式以外的并發(fā)特性,不能支持并發(fā)編程的語(yǔ)言將逐步走向消亡,而僅僅能在現(xiàn)代硬件不重要的場(chǎng)合占得一席之地。
并發(fā)比面向?qū)ο鬀_擊更大的第二個(gè)原因是,盡管順序化編程已經(jīng)很難,但并發(fā)編程更難。例如,在分析順序化程序時(shí),環(huán)境相關(guān)性分析是考量環(huán)境影響行為的基礎(chǔ)技術(shù)。并發(fā)程序則還需要進(jìn)行同步分析,而同時(shí)做環(huán)境相關(guān)性和同步分析已經(jīng)被證明不可企及
最后,人類(lèi)在遭受并發(fā)的突然襲擊后,發(fā)現(xiàn)并發(fā)程序比順序化代碼難以把握得多。即使最細(xì)心的程序員,也很可能考慮不到簡(jiǎn)單半有序作業(yè)集里的交叉問(wèn)題。
客戶端和服務(wù)端應(yīng)用的差別
對(duì)客戶端應(yīng)用來(lái)說(shuō),并發(fā)是一個(gè)挑戰(zhàn)。但是在很多服務(wù)端程序里,并發(fā)則是一個(gè)“已經(jīng)解決的問(wèn)題”,我們可以例行公事般地構(gòu)造并發(fā)應(yīng)用,結(jié)果它就能很好工作,盡管進(jìn)一步改進(jìn)程序以確保其更具擴(kuò)展性,仍需艱巨努力。這些程序通常都包含大量并行,因?yàn)樗鼈兺瑫r(shí)處理的是大量的彼此無(wú)關(guān)的請(qǐng)求。比如Web服務(wù)器和網(wǎng)站,運(yùn)行的是同樣代碼的副本,處理絕大多數(shù)時(shí)候彼此無(wú)關(guān)的數(shù)據(jù)。
而且,它們的執(zhí)行環(huán)境被隔離,通過(guò)高度支持并發(fā)存取結(jié)構(gòu)化數(shù)據(jù)的數(shù)據(jù)庫(kù)之類(lèi)的抽象數(shù)據(jù)訪問(wèn)方式實(shí)現(xiàn)狀態(tài)共享。因此,代碼通過(guò)數(shù)據(jù)庫(kù)實(shí)現(xiàn)數(shù)據(jù)共享,就能得到“安寧從容的感覺(jué)”——就好像運(yùn)行在一個(gè)整潔、單線程的世界一樣。
而客戶端應(yīng)用的世界里,則沒(méi)有那么規(guī)則和結(jié)構(gòu)化。通常,客戶端程序?yàn)閱斡脩魣?zhí)行相關(guān)的小型運(yùn)算,由此出發(fā),我們發(fā)現(xiàn)可以通過(guò)將運(yùn)算分割為多個(gè)更有效率的小片斷來(lái)實(shí)現(xiàn)并發(fā)。也就是說(shuō)讓用戶界面和程序運(yùn)算小片斷可能以多種方式交互和共享數(shù)據(jù)。但這類(lèi)程序難以并發(fā)執(zhí)行,因?yàn)槠浯a是非均態(tài)的,結(jié)構(gòu)密織、交互復(fù)雜,而且操作的是基于指針的數(shù)據(jù)結(jié)構(gòu)。
并行
·編程模型(Programming Models)
現(xiàn)在,你能用多種方式實(shí)現(xiàn)并行,但每種方式僅僅適用于特定類(lèi)型程序。大多數(shù)時(shí)候,沒(méi)有細(xì)致入微的設(shè)計(jì)與分析,很難事先知道哪種模型適合給定問(wèn)題。如果無(wú)法清楚確定給出的問(wèn)題能套用哪種模型,往往就必須將多個(gè)模型雜合起來(lái)靈活運(yùn)用。
這些并行編程模型可以從以下兩個(gè)方面明確加以區(qū)分:并行作業(yè)的粒度,和任務(wù)間的耦合程度。這兩個(gè)方面的不同,造就了迥異的編程模型。我們接下來(lái)依次討論。
并行作業(yè),小到單個(gè)指令,比如加法和乘法運(yùn)算,大到需花費(fèi)數(shù)小時(shí)甚至數(shù)天的程序。顯然,并行體系中小作業(yè)的累計(jì)耗費(fèi)是巨大的,因此,像并行指令運(yùn)算等一般要求用硬件實(shí)現(xiàn)。與多處理器結(jié)構(gòu)相比,多核處理器減少了通訊和同步消耗,因此減少了小片代碼的累計(jì)成本。同樣,一般來(lái)說(shuō),粒度劃分越小,就越要注意拆分任務(wù)、以及為它們提供彼此間通訊和同步的成本。
另一方面是作業(yè)在通訊和同步時(shí)的耦合程度。不要有這樣的幻想:作業(yè)完全獨(dú)立執(zhí)行,最后產(chǎn)生完全不同的輸出。在這種情況下,各作業(yè)有序執(zhí)行,不會(huì)引發(fā)同步和通訊問(wèn)題,很容易實(shí)現(xiàn)無(wú)數(shù)據(jù)競(jìng)爭(zhēng)可能的編程。這樣的情況是罕見(jiàn)的,絕大多數(shù)程序的并發(fā)作業(yè)都要共享數(shù)據(jù)。因此作業(yè)更為變化多端,保證其正確和高效的復(fù)雜性也大為增加。最簡(jiǎn)單的情況是每個(gè)任務(wù)執(zhí)行完全一樣的代碼。這種類(lèi)型的共享常常是規(guī)則的,通過(guò)對(duì)單個(gè)任務(wù)的分析就能理解。更具挑戰(zhàn)性的是不規(guī)則并行,這個(gè)時(shí)候,各作業(yè)的情況互不相同,共享模式更難于理解。
·無(wú)依賴并行(Independent parallelism)
可能最簡(jiǎn)單、只具有基本行為的模型是無(wú)依賴并行(有時(shí)候也叫密集并行(EP,Embarrassingly Parallel)),一個(gè)或者多個(gè)作業(yè)獨(dú)立運(yùn)行,處理分離的數(shù)據(jù)。
小粒度數(shù)據(jù)并行依賴于并發(fā)執(zhí)行的作業(yè)的獨(dú)立性。它們應(yīng)該無(wú)輸入輸出數(shù)據(jù)的共享、無(wú)交叉執(zhí)行,比如:
double A[100][100];
…
A = A * 2;
求得100×100數(shù)組每個(gè)元素與2的乘積,然后保存到原元素位置。100000次的乘法運(yùn)算都獨(dú)立運(yùn)行,彼此沒(méi)有交叉。它可能是比大多數(shù)計(jì)算機(jī)的實(shí)際需要更理想化的并行模型,粒度很小,因此實(shí)際應(yīng)用中通常會(huì)將矩陣分成n×m個(gè)塊,然后在這些塊的基礎(chǔ)上執(zhí)行并行計(jì)算。
而在粒度軸的另一端,很多應(yīng)用,比如搜索引擎,共享僅僅一個(gè)巨型只讀數(shù)據(jù)庫(kù),因此要求并行查詢沒(méi)有交叉。同樣,大規(guī)模仿真系統(tǒng)要求很多并行程序同時(shí)訪問(wèn)包含輸入?yún)?shù)的大型空間,這是一個(gè)讓人頗感棘手的并行應(yīng)用。
·規(guī)則并行(Regular parallelism)
比無(wú)依賴并行略為復(fù)雜的是計(jì)算工作彼此依賴時(shí),將同樣的操作應(yīng)用到一個(gè)數(shù)據(jù)集上。如果在兩個(gè)操作間需要通訊或同步,則操作間存在依賴。
我們考慮以下用四個(gè)相鄰元素的平均值替換數(shù)組中每個(gè)元素的運(yùn)算模型:
A[i, j] = (A[i-1, j] + A[i, j-1] + A[i+1, j] + A[i, j+1]) / 4;
這類(lèi)運(yùn)算要求細(xì)致協(xié)調(diào),確保在用平均值替換前,已經(jīng)準(zhǔn)備好數(shù)組中相鄰數(shù)據(jù)。如果不考慮空間消耗,可以將計(jì)算得到的平均值寫(xiě)入一個(gè)新數(shù)組。一般,其他更結(jié)構(gòu)化的計(jì)算策略,比如用Diagonal Wavefront算法訪問(wèn)數(shù)組,會(huì)得到同樣的結(jié)果,而且有更好的緩存尋址和更低的內(nèi)存消耗收益。
規(guī)則并行程序可能需要同步控制,或者精心排練的執(zhí)行策略,以確保得到正確的結(jié)果,但不像普通并行,它可以通過(guò)分析隱藏在操作后面的代碼以確定怎樣實(shí)現(xiàn)并行,并知道如何共享數(shù)據(jù)。
再次移到粒度軸的另一端,譬如Web站點(diǎn)上的運(yùn)算工作,除了訪問(wèn)相同數(shù)據(jù)庫(kù),存在典型的獨(dú)立性。因此除了數(shù)據(jù)庫(kù)事務(wù),所有的運(yùn)算都并行進(jìn)行,不需要大量協(xié)調(diào)工作。
·無(wú)結(jié)構(gòu)并行(Unstructured parallelism)
大多數(shù)情況下,最沒(méi)有規(guī)律的并行形式是并行運(yùn)算彼此不同,因此它們的數(shù)據(jù)訪問(wèn)無(wú)可預(yù)測(cè),需要通過(guò)顯式同步加以協(xié)調(diào)。這是使用多線程和顯式同步編寫(xiě)的程序里最常見(jiàn)的并行形式,其中的線程在程序里扮演著互不相同的角色。一般,對(duì)于這種并行形式,除了知道兩個(gè)線程訪問(wèn)數(shù)據(jù)發(fā)生沖突時(shí)需要顯式同步,我們?cè)谄渌矫婧茈y說(shuō)出個(gè)頭頭道道;另外,這類(lèi)程序具有不確定性。
鎖
·狀態(tài)共享存在的問(wèn)題;鎖的局限性
無(wú)結(jié)構(gòu)并行面臨的又一個(gè)挑戰(zhàn)來(lái)源于無(wú)結(jié)構(gòu)狀態(tài)共享。客戶端應(yīng)用通常通過(guò)共享內(nèi)存的辦法來(lái)解決對(duì)象圖(Graphs of Objects)中交互行為無(wú)可預(yù)測(cè)的問(wèn)題。
假設(shè)兩個(gè)任務(wù)希望訪問(wèn)同一個(gè)對(duì)象,其中一個(gè)可以修改對(duì)象的狀態(tài),如果我們不加控制,那么就會(huì)產(chǎn)生數(shù)據(jù)競(jìng)爭(zhēng)。競(jìng)爭(zhēng)的后果很糟糕,并發(fā)任務(wù)可能讀寫(xiě)到不一致或已損毀的數(shù)據(jù)。
有大量同步策略可以解決數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題,其中最簡(jiǎn)單的就是鎖。每一個(gè)需要訪問(wèn)共享數(shù)據(jù)片的任務(wù)在訪問(wèn)數(shù)據(jù)前必須申請(qǐng)得到鎖,然后執(zhí)行計(jì)算;最后要釋放鎖,以便其他任務(wù)可以對(duì)這些數(shù)據(jù)執(zhí)行別的操作。不幸的是,盡管鎖在一定程度上能避免數(shù)據(jù)競(jìng)爭(zhēng),但它也給現(xiàn)代軟件開(kāi)發(fā)帶來(lái)了嚴(yán)重問(wèn)題。
最主要的問(wèn)題是,鎖不具有可組合性。你不能保證由兩部分以鎖為基礎(chǔ)、能正確運(yùn)行的代碼合并得到的程序依然正確。而現(xiàn)代軟件開(kāi)發(fā)的基礎(chǔ)恰恰是將小程序庫(kù)合并為更大程序的組裝能力;因此,我們無(wú)法做到不考察組件的具體實(shí)現(xiàn),就能在它們基礎(chǔ)上組裝大軟件,這是個(gè)大問(wèn)題。
造成組裝失敗的禍?zhǔn)资撬梨i。考慮最簡(jiǎn)單的情況,當(dāng)兩個(gè)任務(wù)以相反順序申請(qǐng)兩個(gè)鎖時(shí),死鎖就出現(xiàn)了:任務(wù)T1獲得了鎖L1,任務(wù)T2獲得了鎖L2,然后,T1申請(qǐng)獲得鎖L2,同時(shí)T2申請(qǐng)獲得L1。此時(shí),兩個(gè)任務(wù)將永久阻塞。而被以相反順序請(qǐng)求兩個(gè)鎖的情況在任何時(shí)候都可能發(fā)生,所以獲得一個(gè)鎖后,只有讓調(diào)用進(jìn)入你無(wú)法控制代碼的內(nèi)部,才可能找到解決死鎖問(wèn)題的辦法。
但是,那些可擴(kuò)展的框架卻沒(méi)有考慮這個(gè)問(wèn)題。即使目前最根正苗紅的商業(yè)應(yīng)用框架也是這么干的,包括.NET框架、Java標(biāo)準(zhǔn)庫(kù)等。之所以沒(méi)出什么大問(wèn)題,是因?yàn)殚_(kāi)發(fā)人員仍然沒(méi)有編寫(xiě)要求頻繁鎖定的大量并發(fā)的程序。很多復(fù)雜模型希望解決死鎖問(wèn)題——比如實(shí)現(xiàn)退避/重送(backoff-and-retry)協(xié)議——但這些模型都需要程序員經(jīng)受?chē)?yán)格訓(xùn)練,并且一些解決辦法還可能引入其他問(wèn)題(比如活鎖(或空轉(zhuǎn),livelock))。
通過(guò)確保所有鎖申請(qǐng)都只能在安全順序基礎(chǔ)上得到滿足的死鎖預(yù)防技術(shù),也無(wú)能為力。例如鎖調(diào)整與鎖分級(jí)策略,要求所有同級(jí)鎖按照預(yù)定順序一次滿足,此后只能申請(qǐng)獲得更高級(jí)別的單一鎖,以此的確可以避免鎖沖突。這類(lèi)技術(shù)雖然在實(shí)踐中還未普及,不過(guò)在單一團(tuán)隊(duì)維護(hù)的模塊和框架內(nèi)已經(jīng)發(fā)揮了作用。但是,它們要求對(duì)全部代碼的完全控制。這就嚴(yán)重限制了這些技術(shù)在可擴(kuò)展框架、插件系統(tǒng)和其他需要將多方編寫(xiě)的代碼整合環(huán)境里的應(yīng)用。
一個(gè)更基本問(wèn)題是,鎖的實(shí)現(xiàn)依賴于程序員對(duì)協(xié)定的嚴(yán)格遵循。鎖與它所保護(hù)的數(shù)據(jù)間關(guān)系相當(dāng)隱諱,只能通過(guò)程序員的紀(jì)律性得到維系。程序員必須總能清楚記得訪問(wèn)共享數(shù)據(jù)前,要在正確地點(diǎn)放置恰當(dāng)?shù)逆i。有時(shí),程序里的鎖管理協(xié)定的確存在,但它們幾乎從來(lái)沒(méi)有精確到可供工具實(shí)現(xiàn)檢驗(yàn)。
鎖還有其他一些更加微妙的問(wèn)題。鎖定是一個(gè)全局屬性,很難被本地化到單個(gè)過(guò)程、類(lèi)或者框架。所有訪問(wèn)共享數(shù)據(jù)片的代碼都必須知道且服從鎖約定,無(wú)論是誰(shuí)編寫(xiě)這些代碼,代碼用在什么地方。
即便將同步本地化,也并非任何時(shí)候都能正常工作。拿一個(gè)常見(jiàn)解決方案,如Java中的synchronized方法來(lái)說(shuō)。對(duì)象的每個(gè)方法都能從對(duì)象得到一個(gè)鎖,所以沒(méi)有任何兩個(gè)線程可以同時(shí)直接訪問(wèn)對(duì)象的狀態(tài)。而對(duì)象狀態(tài)僅能通過(guò)對(duì)象的方法訪問(wèn),程序員又記得給方法增加了synchronized聲明,如此一來(lái),看似達(dá)到了我們的目的。
而實(shí)際上,synchronized方法至少有三個(gè)主要的問(wèn)題。首先,它們不適合于其方法會(huì)調(diào)用別的對(duì)象(比如Java的Vector和.NET的SyncHashTable)的虛函數(shù)的類(lèi)型,因?yàn)楂@得一個(gè)鎖后調(diào)用進(jìn)入第三方代碼,就可能引發(fā)死鎖。其次,synchronized方法可能導(dǎo)致頻繁鎖定,因?yàn)樗鼈円笤谒械膶?duì)象實(shí)例上獲得和釋放鎖,即便很多對(duì)象從不存在線程交互。第三,synchronized方法的鎖粒度過(guò)小,當(dāng)程序在單或多個(gè)對(duì)象上調(diào)用多個(gè)方法時(shí),無(wú)法保證操作的原子性。我們以如下簡(jiǎn)單的銀行事務(wù)為例:
account1.Credit(amount); account2.Debit(amount)
逐對(duì)象(Per-object)鎖定可以保護(hù)每個(gè)調(diào)用,但不能阻止其他線程看到兩個(gè)賬戶在多次調(diào)用之間的不一致信息。這種類(lèi)型的操作,原子性與單個(gè)調(diào)用的邊界不吻合,因此需要額外的、顯式同步。
·鎖的替代者
考慮到內(nèi)容完整性,我得提一下鎖的兩個(gè)主要備選方案。一個(gè)是無(wú)鎖編程(Lock-Free Programming)。通過(guò)對(duì)處理器內(nèi)存模式的深入認(rèn)識(shí),建立可共享但無(wú)顯式鎖定的數(shù)據(jù)結(jié)構(gòu)是可能的。無(wú)鎖編程難度很大且非常脆弱,因此新的無(wú)鎖數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)方案,仍在不斷修改之中。
第二個(gè)替代物是事務(wù)內(nèi)存(Transactional Memory),它將數(shù)據(jù)庫(kù)中事務(wù)模式的核心思想移植到編程語(yǔ)言里來(lái)。程序員將自己的程序編寫(xiě)為一個(gè)個(gè)具有確定原子性的塊,它們可以分離執(zhí)行,因此只有在每個(gè)原子行為執(zhí)行前和執(zhí)行后,并發(fā)操作才能訪問(wèn)共享數(shù)據(jù)。盡管很多人看好事務(wù)內(nèi)存的前途,但它目前仍處于研究之中。
對(duì)語(yǔ)言的要求
·我們需要何種編程語(yǔ)言
我們需要更高層次的語(yǔ)言抽象,要讓目前的命令式語(yǔ)言進(jìn)化式擴(kuò)展,這樣,現(xiàn)存的應(yīng)用才能快速實(shí)現(xiàn)并發(fā)執(zhí)行。無(wú)論是初始開(kāi)發(fā)階段,還是維護(hù)階段,編程模型都必須讓并發(fā)易于理解和實(shí)現(xiàn)。
·顯式、隱式和自動(dòng)并行
顯式編程模型提供的抽象要求程序員能明確定位并發(fā)出現(xiàn)的位置。其主要優(yōu)點(diǎn)是,它允許程序員充分利用各自在應(yīng)用領(lǐng)域的知識(shí),充分挖掘應(yīng)用的并發(fā)潛能。不足在于面對(duì)共享數(shù)據(jù)時(shí),要求程序員高度熟練,以實(shí)現(xiàn)新的更高層次的編程抽象模型。
隱式編程模型將并發(fā)隱藏在庫(kù)和API包內(nèi)部,因此在庫(kù)以并行方式執(zhí)行工作時(shí),程序員得以持續(xù)保有全局視野。這種方式可以讓初級(jí)程序員安全使用并發(fā)。主要弱點(diǎn)是難以獲得并發(fā)的全部性能收益。另外,也很難設(shè)計(jì)出在任何場(chǎng)合都不暴露并發(fā)的接口——比如,當(dāng)程序?qū)⒉僮鲬?yīng)用在相同數(shù)據(jù)的多個(gè)實(shí)例時(shí)。
另一個(gè)正被廣泛研究的方法是自動(dòng)并行,編譯器試圖自動(dòng)找到并行部分,通常應(yīng)用于Forthan之類(lèi)老式語(yǔ)言編寫(xiě)的程序里。聽(tīng)起來(lái)很吸引人哦,但這種辦法在實(shí)踐中不大行得通。必須有對(duì)程序的精確分析,才能弄清程序的潛在行為。而對(duì)Forthan之類(lèi)簡(jiǎn)單語(yǔ)言做這類(lèi)分析就頗具挑戰(zhàn)性了,面對(duì)像C這樣的以指針為基礎(chǔ)操作數(shù)據(jù)的語(yǔ)言,更是難上加難。再說(shuō),順序式程序通常使用的是順序式算法,能實(shí)現(xiàn)并發(fā)的部分非常有限。
·命令式和函數(shù)式語(yǔ)言
流行商用編程語(yǔ)言(如Pascal、C、C++、Java和C#)都是命令式語(yǔ)言,在這些語(yǔ)言里,程序員分步指定對(duì)變量和數(shù)據(jù)結(jié)構(gòu)的變化。小粒度控制構(gòu)造(如循環(huán))、低層次數(shù)據(jù)操作和共享易變對(duì)象實(shí)例等因此,使以這些語(yǔ)言編寫(xiě)的程序難以分析并實(shí)現(xiàn)自動(dòng)并行。
我們通常相信函數(shù)式語(yǔ)言,如Scheme、ML和Haskell等,能夠鏟除這類(lèi)障礙,因?yàn)樗鼈兙蜑椴l(fā)而生。以這些語(yǔ)言寫(xiě)成的程序,操作沒(méi)有并發(fā)危險(xiǎn)、不需改變的對(duì)象實(shí)例。此外,它們沒(méi)有副作用,執(zhí)行順序上的限制很少。
但實(shí)際中,函數(shù)式語(yǔ)言并不一定有益于并發(fā)。函數(shù)式程序中的并行主要執(zhí)行于過(guò)程調(diào)用層次,這對(duì)于傳統(tǒng)并行處理器來(lái)說(shuō),粒度過(guò)小。而且,大多數(shù)函數(shù)式語(yǔ)言允許操作可變對(duì)象時(shí)存在部分副作用,因此應(yīng)用了這些特性的代碼,也難以實(shí)現(xiàn)自動(dòng)并行。
這些語(yǔ)言為了增加表達(dá)能力和提高效率又引入了可變狀態(tài)。在純粹的函數(shù)式語(yǔ)言里,復(fù)合數(shù)據(jù)結(jié)構(gòu),如數(shù)組、樹(shù)等,是通過(guò)原結(jié)構(gòu)副本外加待修改數(shù)據(jù)實(shí)現(xiàn)更新的。這種方法從語(yǔ)義上講頗有魅力,但性能糟糕(線性算法很容易升級(jí)為二階算法)。此外,函數(shù)式更新對(duì)嚴(yán)格的順序式運(yùn)算無(wú)能為力,此時(shí),每個(gè)操作都會(huì)等待至上一個(gè)操作完成對(duì)程序狀態(tài)的更新才能執(zhí)行。
函數(shù)式語(yǔ)言對(duì)于并發(fā)的真正貢獻(xiàn)是這些語(yǔ)言中廣泛采用的高層次編程方式,在這種方式下,像Map和Map-Reduce等操作會(huì)將計(jì)算應(yīng)用于復(fù)合數(shù)據(jù)結(jié)構(gòu)的所有元素。這種高層次的操作方式是并發(fā)的堅(jiān)實(shí)基礎(chǔ)。這種編程風(fēng)格,幸運(yùn)的是,這種編程方式?jīng)]有被固化于函數(shù)式語(yǔ)言,對(duì)命令式語(yǔ)言有重要借鑒意義。
例如,Google的Jeffrey Dean和Sanjay Ghemawat描述過(guò)Google如何使用Map-Reduce實(shí)現(xiàn)大規(guī)模分布式計(jì)算。命令式語(yǔ)言可以開(kāi)動(dòng)腦筋將這些特性納為己用,并從中獲益。這一點(diǎn)重要,畢竟,我們的工業(yè)不能從頭再來(lái)。為了保護(hù)全世界對(duì)現(xiàn)存軟件的巨大投資,特別需要盡快增加對(duì)并發(fā)的支持,同時(shí)保護(hù)軟件開(kāi)發(fā)人員擁有的命令式語(yǔ)言專業(yè)知識(shí)和經(jīng)驗(yàn)。
抽象優(yōu)化
目前的大多數(shù)語(yǔ)言都在線程和鎖層次實(shí)現(xiàn)了顯式編程。這種抽象層次過(guò)低且難以系統(tǒng)化。這種架構(gòu)不足以成為構(gòu)建抽象的堅(jiān)實(shí)基礎(chǔ),它縱容多線程程序隨意阻塞和重入(reentrancy),以致帶來(lái)很多問(wèn)題。
更高層次抽象允許程序員用自然方式表述任務(wù),此時(shí),運(yùn)行時(shí)系統(tǒng)能將這些任務(wù)有計(jì)劃調(diào)度,使之適配于計(jì)算機(jī)硬件。這將使應(yīng)用在新的硬件上獲得更好性能。另外,在常規(guī)開(kāi)發(fā)中,程序員將得閑將精力放在任務(wù)內(nèi)部的順序執(zhí)行流程上來(lái)。
有關(guān)更高層次抽象的兩個(gè)基本例子是異步調(diào)用和future。無(wú)阻塞函數(shù)和方法產(chǎn)生的是異步調(diào)用。調(diào)用者可繼續(xù)執(zhí)行,從概念上說(shuō),也就是消息被發(fā)送到任務(wù)或者fork進(jìn)程去獨(dú)立執(zhí)行操作。期貨(future)是一種從異步調(diào)用返回操作結(jié)果的機(jī)制,但目前僅是一個(gè)有價(jià)值的概念,還未具體實(shí)現(xiàn)。
更高層次抽象的又一個(gè)例子是主動(dòng)對(duì)象(Active Object),它運(yùn)行在自有的線程里,因此創(chuàng)建1000個(gè)這樣的對(duì)象,就相當(dāng)于創(chuàng)建了1000個(gè)執(zhí)行線程。主動(dòng)對(duì)象僅有一個(gè)方法在給定時(shí)間執(zhí)行,它扮演監(jiān)視器的角色,但不要求傳統(tǒng)意義上的鎖定。相反,來(lái)源于主動(dòng)對(duì)象外部的方法調(diào)用是通過(guò)此對(duì)象匯集、編隊(duì)和派發(fā)異步消息實(shí)現(xiàn)的。從專業(yè)化的Actor語(yǔ)言到傳統(tǒng)C代碼的STA(Single-Threaded Apartment)式COM套件等,主動(dòng)對(duì)象有很多種設(shè)計(jì)實(shí)現(xiàn)。
其他更高層次抽象也是需要的,比如描述和檢測(cè)異步消息交換的協(xié)議。所有這些方法應(yīng)該被整合起來(lái)構(gòu)建一個(gè)統(tǒng)一的編程模型,從而滿足各主要粒度層次的典型應(yīng)用的并發(fā)要求。
對(duì)工具的要求
·我們需要何種工具
因?yàn)椴⑿芯幊痰墓逃须y度和我們對(duì)它的不熟悉,必然需要更好的編程工具系統(tǒng)地幫助我們查漏補(bǔ)缺、調(diào)試程序、尋找性能瓶頸和測(cè)試程序。沒(méi)有這些工具,并發(fā)將成為提高開(kāi)發(fā)和測(cè)試人員生產(chǎn)率的障礙,導(dǎo)致并發(fā)軟件成本更高,而質(zhì)量更低劣。
并發(fā)會(huì)帶來(lái)不同于順序式代碼的新型程序錯(cuò)誤。數(shù)據(jù)競(jìng)爭(zhēng)(同步不足或死鎖造成)和活鎖(不適當(dāng)同步造成)缺陷難以發(fā)現(xiàn)和理解,因?yàn)槠湫袨橥ǔ>哂胁淮_定性且難以重現(xiàn)。傳統(tǒng)調(diào)試方法,比如啟動(dòng)前設(shè)置斷點(diǎn)再重新運(yùn)行程序,對(duì)于執(zhí)行路徑和行為每次都可能發(fā)生變化的并發(fā)程序來(lái)說(shuō),無(wú)能為力。
在并發(fā)世界里,系統(tǒng)級(jí)的缺陷檢測(cè)工具具有重大價(jià)值。這些工具利用靜態(tài)程序分析法系統(tǒng)地探測(cè)程序所有可能的執(zhí)行行為,因此它們能夠捕捉到不能重現(xiàn)的錯(cuò)誤。盡管類(lèi)似技術(shù),比如模型檢測(cè),已經(jīng)在天生支持并發(fā)的硬件的缺陷探測(cè)中獲得重大成功,但在軟件中非常困難。大多數(shù)情況下,軟件的狀態(tài)空間比硬件大得多,因此這些系統(tǒng)探測(cè)虛擬狀態(tài)的技術(shù)還有很多工作要做。無(wú)論是硬件還是軟件,模塊化和抽象都是提高分析可行性的關(guān)鍵。在硬件模型測(cè)試中,如果你能將ALU (arithmetic logic unit)拆分為一個(gè)個(gè)寄存器做獨(dú)立分析,那么你的任務(wù)就變得很容易實(shí)現(xiàn)了。
這就引出了軟件更難以分析的第二個(gè)原因:將軟件切分為眾多片段,做獨(dú)立分析,然后合并結(jié)果去看它們?nèi)绾我黄鸸ぷ饕y得多。共享狀態(tài)、不確定接口和格式不明的交互將使軟件分析任務(wù)的挑戰(zhàn)性大大增加。
目前,并發(fā)軟件的缺陷檢測(cè)工具是個(gè)熱門(mén)研究領(lǐng)域。其中,微軟研究院的KISS(Keep it Strictly Sequential)是項(xiàng)有前途的技術(shù),它將多線程程序轉(zhuǎn)化為包括了原始的不超過(guò)兩個(gè)切換環(huán)境的線程的所有可能行為的順序式程序。轉(zhuǎn)換后的程序可以被大量現(xiàn)存的順序式工具分析,因此這些工具得以完成有限模型的并發(fā)缺陷檢測(cè)工作。
即便我們?nèi)〉昧松鲜鲆恍┏煽?jī),程序員仍然需要優(yōu)秀的調(diào)試工具幫助他們理解并行程序里的復(fù)雜和難以重現(xiàn)的交互行為。有兩項(xiàng)常規(guī)技術(shù)可以收集這方面信息。一是更好的日志跟蹤工具,可以記錄哪一個(gè)消息被送到了哪個(gè)進(jìn)程或線程,進(jìn)程和線程又訪問(wèn)了哪個(gè)對(duì)象,因此開(kāi)發(fā)者可以回溯并部分理解程序的執(zhí)行行為。開(kāi)發(fā)者也希望獲得追蹤跨線程因果關(guān)系(比如哪個(gè)消息傳遞到了主動(dòng)對(duì)象,何時(shí)執(zhí)行,導(dǎo)致哪一個(gè)別的消息傳遞到了其他主動(dòng)對(duì)象)、回放、重排隊(duì)列里的消息、步進(jìn)包含回調(diào)的異步調(diào)用模式,以及其他能力,借以檢測(cè)代碼的并發(fā)執(zhí)行行為。第二個(gè)辦法是重放執(zhí)行,它能讓程序員備份程序的執(zhí)行歷史,然后可重新執(zhí)行一些代碼。重放調(diào)試的想法由來(lái)已久,但它的高成本和復(fù)雜性導(dǎo)致了被拋棄的下場(chǎng)。最近,虛擬機(jī)監(jiān)視器(VMM,Virtual Machine Monitor)已經(jīng)逐漸呈現(xiàn)出取代這兩項(xiàng)技術(shù)的趨勢(shì),未來(lái)的并發(fā)世界里,這項(xiàng)技術(shù)很可能成為必需品。
并發(fā)世界也需要性能調(diào)試和調(diào)節(jié)方面的新工具。并發(fā)引入了新的性能瓶頸,比如鎖競(jìng)爭(zhēng)(Lock Contention)、緩存一致性(Cache Coherence)管理和鎖護(hù)送效應(yīng)(Lock Convoys)等,這些問(wèn)題依靠簡(jiǎn)單工具是難以察覺(jué)的。對(duì)計(jì)算機(jī)底層和程序并發(fā)結(jié)構(gòu)更為敏感的新工具,將幫助我們更好的發(fā)現(xiàn)這些問(wèn)題。
測(cè)試領(lǐng)域,也必須發(fā)生變化。并發(fā)程序,因其不確定性行為,更難于測(cè)試。用以跟蹤語(yǔ)句和分支是否得到執(zhí)行的簡(jiǎn)單代碼覆蓋測(cè)試方法,需要被擴(kuò)展以支持對(duì)其他并行執(zhí)行代碼的評(píng)估,否則測(cè)試將提供不符實(shí)際的反應(yīng)程序執(zhí)行完整度的優(yōu)化結(jié)果圖。另外,簡(jiǎn)單壓力測(cè)試也需要通過(guò)更多地使用類(lèi)似模塊檢測(cè)技術(shù)以探測(cè)系統(tǒng)狀態(tài)空間的系統(tǒng)級(jí)技術(shù)加以擴(kuò)充。比如,Verisoft使用這些技術(shù)在尋找電話交換軟件錯(cuò)誤方面已經(jīng)取得相當(dāng)成功。現(xiàn)在,很多并發(fā)應(yīng)用通過(guò)加大測(cè)試時(shí)的壓力大小來(lái)建立應(yīng)用不太可能遭受?chē)?yán)重競(jìng)爭(zhēng)的信心。未來(lái),這將愈加顯得不夠,軟件開(kāi)發(fā)者將必須以嚴(yán)格的確定性測(cè)試代替基于壓力測(cè)試的想當(dāng)然,來(lái)證明他們產(chǎn)品的質(zhì)量。
并行是關(guān)鍵
并發(fā)解決方案將是軟件史上的一次重大變革。其難點(diǎn)不在于多核硬件的構(gòu)建,二是讓主流應(yīng)用從持續(xù)爆炸式增長(zhǎng)的CPU性能中獲益的編程方式。
軟件工業(yè)必須重歸現(xiàn)存應(yīng)用在新硬件上更快運(yùn)行的跑道。為此,我們必須開(kāi)始學(xué)習(xí)編寫(xiě)包含至少數(shù)十最好數(shù)百可分離任務(wù)(當(dāng)然不是說(shuō)同時(shí)處于活動(dòng)狀態(tài))的并發(fā)應(yīng)用。
并發(fā)也開(kāi)啟了新的可能,那就是讓軟件功能更多、能力更強(qiáng)。這就要求我們積極發(fā)揮想象力,找到挖掘并利用新處理器指數(shù)級(jí)增長(zhǎng)的潛能的辦法。
為了促成軟件目標(biāo)的早日實(shí)現(xiàn),程序語(yǔ)言設(shè)計(jì)師、系統(tǒng)構(gòu)建者和編程工具創(chuàng)造者需要開(kāi)始努力思考并行,找到比目前成為并行程序構(gòu)建基礎(chǔ)的低層次線程工具和顯式同步更好的技術(shù)。我們需要更高層次的并行構(gòu)建實(shí)現(xiàn),借此更清楚表述開(kāi)發(fā)人員的意圖,以使程序的并行架構(gòu)更加明晰,更容易理解,更能被工具驗(yàn)證。
posted on 2007-09-23 09:33
小石頭 閱讀(318)
評(píng)論(0) 編輯 收藏 引用