2.0時代的軟件空前地活躍在人類生活的方方面面,從而帶來了更多的計算量。因此2.0不可避免地對計算提出了新的需求、引發(fā)了新的思考,在這其中,多核帶來的并行計算和并發(fā)編程無疑是最為深刻的一點(diǎn)。
我們都能依稀記得,在2005年Sun發(fā)布了代號為Tiger的Java 5。在其眾多的特性之中, JVM的改進(jìn)和java.util.concurrent包的出現(xiàn)無疑非常引人注目。改進(jìn)后的JVM可以使用低層機(jī)器指令取代鎖,精化了互斥訪問的粒度,提高了系統(tǒng)的可伸縮性和活性。而concurrent包提供了大量線程和鎖之上的并發(fā)抽象,比如線程池、閉鎖、信號量、關(guān)卡等。這些可以幫助開發(fā)者快速地構(gòu)建出高效、可伸縮的系統(tǒng)來。就在同一年,還發(fā)生了另一件影響深遠(yuǎn)的事件:AMD首次發(fā)布了其雙核CPU,從而打響了AMD與Intel兩大芯片廠商的多核之爭。
當(dāng)Java遇到多核
早在十多年前,IBM、Sun與HP就已經(jīng)設(shè)計出了雙核處理器,比如IBM于2001年推出的基于雙核的POWER4處理器和Sun的 UltraSPARC芯片,但這些雙核處理器都是用于高端的RISC領(lǐng)域,價格高昂,被大眾使用的X86并沒有享受到它帶來的性能優(yōu)勢。直到Intel和 AMD相繼推出自己的雙核處理器后,X86領(lǐng)域才算是有了自己的多核架構(gòu)。
所謂雙核處理器,簡單地說就是在一塊CPU基板上集成兩個處理器核心,并通過并行總線將各處理器核心連接起來。多核并不是一個新概念,而只是 CMP(Chip Multi Processors,單芯片多處理器)中最基本、最簡單、最容易實(shí)現(xiàn)的一種類型。其實(shí)在RISC處理器領(lǐng)域,多核都早已經(jīng)實(shí)現(xiàn)。
多核與單核的區(qū)別在于,前者可以讓程序真正地“同時”執(zhí)行,而不是多個進(jìn)程輪流使用CPU,從而給用戶造成“多個程序正在同時執(zhí)行”的假象。簡單地說,“ 并發(fā)”就是為了讓程序運(yùn)行得更快。在以前,達(dá)到這個目的的手段通常是依賴CPU時鐘頻率的提升。然而普通單核心處理器的頻率難于提升,性能沒有質(zhì)的飛躍。由于頻率難于提升,Intel在發(fā)布3.8GHz的產(chǎn)品以后只得宣布停止4GHz的產(chǎn)品計劃;而AMD在實(shí)際頻率超過2GHz以后也無法大幅度提升,3GHz成為了AMD無法逾越的一道坎。因此,CPU內(nèi)部開始出現(xiàn)了兩個、四個甚至更多的內(nèi)核。
為了充分發(fā)揮每一個核的效用,應(yīng)用程序需要多個線程同時運(yùn)行來保持CPU核的忙碌。Java可以幫助你在多核系統(tǒng)上構(gòu)建良好的應(yīng)用程序。它可以方便地、相對便宜地創(chuàng)建線程,這非常重要。如果創(chuàng)建線程的開銷比線程完成工作的開銷還要大,那么并發(fā)將變得毫無意義。concurrent包中提供的并發(fā)構(gòu)建塊非常豐富,幾乎覆蓋了所有并發(fā)編程中用到的工具。不僅如此,Java還在快速地演變著,以適應(yīng)更高并發(fā)性系統(tǒng)的構(gòu)建,例如,Java 7中即將加入ForkJoin框架,按照Doug Lea的描述,它專門適用于“>32個CPU(內(nèi)核)的系統(tǒng)”。
除了這些API層面的支持外,Java在并發(fā)編程上的底層平臺上顯得更加野心勃勃,因?yàn)镴ava有自己的存儲模型,這個存儲模型早在Java 1.1時代就存在了,盡管當(dāng)時還不完善,但綷-過多次修補(bǔ)和改進(jìn),已綷-變得非常成熟了。Java存儲模型可以抹平不同硬件平臺提供的存儲模型的差異,嚴(yán)格地定義了線程間通訊的規(guī)范。例如,對于C語言來說,相同的代碼在X86和PowerPC上會有不同的語義。
細(xì)心的讀者可能注意到,Java不過是這場變革的一個突出的代表而已。很多語言都在不同程度上調(diào)整自己,以適應(yīng)2.0計算時代對并發(fā)的需要。在不久,當(dāng)有人問C#之父Anders Hejlsberg,“未來幾年內(nèi)語言的發(fā)展方向在何處”時,Anders表示“要處理好多核的問題,并提供一個更好的并發(fā)模型”。Erlang語言最近也受到越來越多的關(guān)注,這說明人們迫切需要一個強(qiáng)大而又充分簡單的工具來解決并發(fā)編程的挑戰(zhàn)。
以Java語言為代表的編程語言在面對并發(fā)時代所做出的努力是令人激動的,但是相比于硬件,軟件的發(fā)展總是滯后的,人們總是等意識到軟件出現(xiàn)問題了才開始著手改進(jìn)。2.0時代的并發(fā)計算正在向桌面和客戶端轉(zhuǎn)移,但是有多少人做好準(zhǔn)備了?于是有人開始驚呼:狼來了?
關(guān)于“狼來了”的討論,最好的結(jié)論就是無論狼是否真的會來,羊圈的籬笆仍然都要修理。這里的“籬笆”就是指并發(fā)編程技術(shù)。由于是底層的計算平臺正在發(fā)生變化,因此不僅僅是開發(fā)者,包括需求分析人員、設(shè)計者、程序員和測試者都應(yīng)該在工作時考慮到并發(fā)帶來的影響。比如:
我們經(jīng)常要思考,如何才能獲得一個最佳的程序粒度,同時保證它們最大限度地彼此隔離,從而可以簡單地分配到不同的處理器單元上?線程間通訊的內(nèi)容有哪些?等等。要想很好地回答這些問題,僅靠程序員或者設(shè)計者是不夠的。它需要從獲取需求開始,就著力對任務(wù)進(jìn)行劃分。系統(tǒng)對于并發(fā)性的要求,很難在開發(fā)的中后期通過“重構(gòu)”來引入,因此必須在設(shè)計之初就給予關(guān)注。評價軟件有很多標(biāo)準(zhǔn),比如可擴(kuò)展性、模塊化、松耦合等等,今天還要考慮軟件是否有足夠的并發(fā)性,以充分利用底層的計算資源。它必將成為衡量軟件質(zhì)量的重要標(biāo)準(zhǔn)之一。
編寫并發(fā)程序需要面對很多挑戰(zhàn)。尤其是多核的流行,它使程序中可以有多個線程真正地 “同時”運(yùn)行。因此開發(fā)者要面對的一個最大挑戰(zhàn)是劃分任務(wù)。也許你需要對數(shù)據(jù)進(jìn)行劃分,清晰地識別出任務(wù)邊界,還要盡可能地讓每個任務(wù)在執(zhí)行時只使用自己的數(shù)據(jù)。如果不同的線程要共享數(shù)據(jù),問題將迅速變得復(fù)雜。你無法再像以前那樣,只要等上幾個月,就可以換上更強(qiáng)勁的處理器,從而讓你的程序運(yùn)行的更快。今天,“免費(fèi)的午餐”已經(jīng)結(jié)束了。能不能把并行化的工作完全交給操作系統(tǒng)和編譯器呢?這是一個充滿誘惑力的愿景-但是,并行化的工作現(xiàn)在無法自動實(shí)現(xiàn),未來也只能在一定程度上有所緩解,而不可能全部交由機(jī)器完成。并行化過程的重點(diǎn)在于分解程序的任務(wù)流和執(zhí)行流,這最終被歸結(jié)為數(shù)學(xué)問題——一個無解的數(shù)學(xué)問題!面對多核,我們固然不能停止讓機(jī)器變得更聰明的努力,但是對于大多數(shù)人來說,更重要的還是充實(shí)自己的知識儲備,適應(yīng)新的思維方式,就像當(dāng)年從過程化編程過渡到面向?qū)ο缶幊虝r所做的那樣。
并發(fā)程序難以開發(fā)是事實(shí),同時測試與調(diào)試更加困難。因?yàn)椴l(fā)錯誤通常更加隱蔽,它們只有在高負(fù)荷和一些特定時序下才會出現(xiàn),而且難以再現(xiàn)。因此,并發(fā)程序的測試需要更多的投入、更好的工具、更精巧的測試策略。通常,并行程序要對需要測試兩個要點(diǎn):性能與安全性。安全性包括程序是否“做了該做的事”、以及是否“沒做不該做的事”兩個方面。而性能是指是否在規(guī)定的時間里做了正確的事。完整的測試計劃應(yīng)該包括:測試計劃、單元測試、代碼審查和靜態(tài)分析等。測試計劃應(yīng)該確保后期的測試行為得以貫徹,同時還要衆(zhòng)-調(diào)測試過程中需要的各種資源。并發(fā)程序測試的難度遠(yuǎn)遠(yuǎn)高于串行程序測試。以單元測試為例,我們幾乎無法只使用唯一的線程完成并發(fā)測試,至少需要兩個線程。然而在JUnit框架,只能識別其自身的線程,對于其他線程拋出的斷言失敗或異常,毫無察覺。因此,測試者在編寫時就要編寫大量衆(zhòng)-調(diào)線程的代碼,這本身就會帶來新的bug,更糟糕的是,不良的測試還會掩蓋被測代碼中的錯誤。
并發(fā),釋放2.0的力量
2.0時代是軟件空前繁榮的時代,軟件將幫助越來越多的人完成越來越多工作。我們也一直在探討解決2.0時代的計算方法。那么,無限度地提升CPU核的時鐘頻率或者在一個CPU內(nèi)加入更多的內(nèi)核,就是2.0時代的計算方法了么?不是。2.0時代的計算方法不僅要有強(qiáng)勁的計算硬件做基礎(chǔ),也要有更優(yōu)秀、更復(fù)雜的軟件做后盾。單靠硬件的發(fā)展是無法滿足計算量的激增的,軟件也必須做出相應(yīng)的調(diào)整。
在多核系統(tǒng)上進(jìn)行并發(fā)編程仍然很困難,它不符合人類的思維方式。縱觀計算機(jī)軟件開發(fā)的歷史,總是伴隨著新技術(shù)推出、開發(fā)者學(xué)習(xí)消化的旋律。從最早的幾十個機(jī)器指令,到今天的結(jié)構(gòu)化語言、面向?qū)ο?、AOP--
2.0時代的另一個效應(yīng)是,它把并行計算和并發(fā)編程擺在了更多的程序員面前。以前,并發(fā)編程還是計算機(jī)科學(xué)家的專利,很多開發(fā)者都將它視為“禁地”。如今,除了復(fù)雜的商業(yè)應(yīng)用和龐大的科學(xué)計算外,桌面端、各種終端都在逐漸地走向高并發(fā)的運(yùn)行環(huán)境。我們可以學(xué)習(xí)新的技術(shù)、新的思路或者新的語言,比如,在上世紀(jì)80年代誕生于愛立信實(shí)驗(yàn)室的Erlang語言,近期就表現(xiàn)出極大的活力。在Erlang中,如果要訪問共享數(shù)據(jù),就需要向數(shù)據(jù)的擁有者發(fā)送一條消息并等待回應(yīng),這種方法在構(gòu)造高可用的并發(fā)系統(tǒng)時已綷-取得了極大的成功。尤其是我們已經(jīng)習(xí)慣了基于對象的抽象。要讓并發(fā)變得容易,就要放棄一些抽象。在此我們不去討論什么是正確的取舍,但是我們正在苦于這樣的幻想:“能不能不做任何取舍呢?”
相對于Web2.0和企業(yè)2.0的波濤洶涌來說,計算2.0顯得有些波瀾不驚。但是,這種處于底層的深刻變化,將會徹底改變上層應(yīng)用的面貌。多核的普及,將使并行/并發(fā)的大行其道,企業(yè)或者個人只有快速抓住這個趨勢,才能順利地暢游于軟件的2.0時代。