——感謝Ian.Sian投遞本文—— 多線程模型是主流的并發(fā)編程模型。在過去幾十年來,多線程模型一直是開發(fā)并發(fā)程序的有力工具。然而,它的歷史并非總那么美好。1997年,NASA 的“火星探路者”號在執(zhí)行任務(wù)的途中遭遇了嚴(yán)重的時(shí)序異常(參見 “What really happend on Mars“,注目 follow-up 中的現(xiàn)身說法),無法發(fā)回探測數(shù)據(jù)。如果不是 NASA 遠(yuǎn)程刷新了程序,它的結(jié)局就只能是報(bào)廢在火星上。這一切都是由程序中潛藏的一個(gè)優(yōu)先級反轉(zhuǎn) bug 造成的。更早的例子還有80年代的一系列 Therac-25 型醫(yī)用粒子加速器事故。在這些加速器釋放出的過量輻射照射之下,數(shù)位病人死亡。事后調(diào)查顯示,至少有一次發(fā)生事故的原因,是加速器的控制軟件中,存在一個(gè)只能由特定操作序列引發(fā)的競爭條件 bug。你也許認(rèn)為這些只是陳年往事,但是直到現(xiàn)在,即便是世界500強(qiáng)公司們高價(jià)買來的信息系統(tǒng),也同樣避免不了這些問題。這導(dǎo)致許多程序員認(rèn)為線程是個(gè)潘多拉魔盒,對它采取能躲就躲的態(tài)度。然而近來計(jì)算機(jī)的發(fā)展使得躲貓貓的空間越來越?。弘S便從市場上淘一個(gè)CPU,它里面也有不止一個(gè)核心。未來的程序員只會(huì)有越來越多的機(jī)會(huì)接觸到并發(fā)編程,而無法再獨(dú)善其身了。 加州大學(xué)伯克利分校教授,愛德華 A. 李在2006年做了一次題為《線程的麻煩 (The Problem with Threads)》的學(xué)術(shù)報(bào)告。在報(bào)告中他提到:看上去,多線程只是對核心語言的小小擴(kuò)展,甚至可以以第三方庫的形式存在。但實(shí)質(zhì)上,多線程程序和原有的核心語言編寫的程序已經(jīng)完全不同了。其原因在于,由于多線程程序可能以任意的次序交錯(cuò)執(zhí)行,程序再也無法像順序執(zhí)行時(shí)那樣產(chǎn)生確定的結(jié)果。多線程程序容易編寫(因?yàn)閷懙氖琼樞虺绦?,但是難分析,難調(diào)試,更容易出錯(cuò)。 在我的想法中,產(chǎn)生問題的根源,是多線程模型作為對并發(fā)問題的一個(gè)抽象,是很不完善的。抽象的實(shí)質(zhì)是對問題的轉(zhuǎn)換。我們可以把抽象應(yīng)用于一個(gè)問題,把它轉(zhuǎn)換成另一個(gè)(或許)更簡單的問題來解決。解決了轉(zhuǎn)換后的簡單問題,就意味著解決了原有的困難問題。嚴(yán)格來說,一個(gè)抽象一定要保存原有問題的結(jié)構(gòu),同時(shí)去除無關(guān)細(xì)節(jié)。但是,由于我們生活的世界并沒有什么東西是完全“嚴(yán)格”的,現(xiàn)實(shí)中使用的抽象有時(shí)會(huì)隱藏解決問題的關(guān)鍵細(xì)節(jié),或者殘留一些不該漏出來的東西。評價(jià)一個(gè)抽象的好壞,也就不止是看它能節(jié)省多少代碼,和它的界面有多優(yōu)美這么簡單,同時(shí)還要看看在一個(gè)問題被抽象轉(zhuǎn)換之后,留了下來的細(xì)節(jié)還能不能好好地解決它。 我們可以從這個(gè)意義上理解為什么線程模型是個(gè)很糟糕的抽象。一方面,對解決問題很關(guān)鍵的細(xì)節(jié)(如執(zhí)行次序)被隱藏起來并受到了粗暴的對待。另一方面,線程模型極力兼容順序程序的設(shè)計(jì)思想也使得如共享變量這樣的,與線程不兼容的細(xì)節(jié)依然殘留在程序員們的視線之內(nèi)。我們無力控制程序的執(zhí)行次序,而我們程序的正確性卻依賴于對共享變量的有序變更??梢哉f,線程提供給我們的抽象簡直是千瘡百孔。我們還能用它干活,只是因?yàn)槲覀兪掷镞€有加鎖機(jī)制,而它可以部分地堵上線程模型的漏洞。諷刺的是,引入加鎖機(jī)制解決問題的同時(shí),又帶來了新的問題,所以我們編寫多線程程序總會(huì)遇上死鎖,活鎖,優(yōu)先級反轉(zhuǎn)……等等。 同樣作為并發(fā)編程問題的抽象,角色模型(Actor Model) 比線程模型好就好在,它的資源分享不像線程模型那樣通過共享變量來進(jìn)行。角色模型中的資源分享只能通過特定的機(jī)制(消息傳遞)來進(jìn)行。你在角色模型里依然可能犯錯(cuò)誤,如你可能制造死鎖,也有可能造成優(yōu)先級反轉(zhuǎn)。但是沒有共享變量就意味著沒有了競爭條件,所以絕大部分資源也用不著上鎖了。這樣一來,原先至關(guān)重要的細(xì)節(jié)變得不那么重要,問題就這么解決了。 一般來說,在修復(fù)一個(gè)糟糕的抽象時(shí),可以采取的策略分如下兩類:
以 MapReduce 為例,它在解決分布式計(jì)算問題時(shí),采取的是第一類策略。與現(xiàn)時(shí)流行的做法相反,MapReduce 并不試圖制造計(jì)算是在單一場所完成的假象(流行話講叫“云計(jì)算”),相反它需要程序員自己把問題拆分到集群中不同的機(jī)器上。同時(shí),它卻隱藏了大量其他細(xì)節(jié)。這種另類策略導(dǎo)致批評 MapReduce “太底層,不通用” 的聲音不絕于耳, 然而這正是 MapReduce 聰明的地方。它放棄面面俱到,集中精力于高效地解決一小類問題(這類問題與排序問題有類似的結(jié)構(gòu)),同時(shí)對其他的問題故意視而不見。它的流行證明了這一策略的成功。 角色模型,通信進(jìn)程(Communicating Sequential Processes, CSP),以及函數(shù)式編程(FP)在應(yīng)對并發(fā)編程問題時(shí)不約而同地選擇了第二類策略。它們采用了與并發(fā)兼容性更好的抽象。角色模型與通信進(jìn)程從線程模型的問題中抹去了共享變量,純粹 FP 則抹掉了“變量”的可變性。CSP 還可以降低程序執(zhí)行次序的不確定性(因?yàn)樵贑SP中執(zhí)行次序默認(rèn)是確定的,不確定性必須在程序設(shè)計(jì)時(shí)顯式聲明)。由于這些努力,這幾種模型都避免了落入線程模型的麻煩中,得到了對并發(fā)問題的更優(yōu)美的解法。我們可以說,這些模型提供的抽象比線程模型的都要好。很遺憾的是,它們盡管優(yōu)美,但卻乏人問津。角色模型與通信進(jìn)程目前不被任何主流操作系統(tǒng)原生支持(微軟在 Windows 7 附帶的新并行運(yùn)行時(shí) ConcRT 中加入了基于角色模型的 Asynchronous Agents Library,使得狀況稍微改觀了一點(diǎn))。FP 的年歲幾乎和計(jì)算機(jī)語言的歷史一樣古老, 但它的市場份額直到現(xiàn)在也小得可憐。 也許一切都是因?yàn)榫€程模型表面上那迷惑人的簡單性,以及墨菲定律的變體:布勞爾技術(shù)慣性定律(已經(jīng)成功的技術(shù)在新的,更好的技術(shù)出現(xiàn)時(shí)也會(huì)賴著不走)。我們曾經(jīng)接納了一個(gè)有缺點(diǎn)的解決方案,而現(xiàn)在我們被捆綁在這個(gè)方案上了。我們?yōu)榫€程模型寫了成百上千萬行的代碼,而現(xiàn)在這些代碼的重量束縛住我們的手腳,使得我們無法前行。 解決線程模型帶來的問題的正確做法,是推廣新的,更完善的模型。既然解決問題的阻礙同時(shí)來自于新技術(shù)的低認(rèn)知度和現(xiàn)有代碼的拖累,很自然地有兩個(gè)方面的工作要做。一、使得新技術(shù)更容易被多數(shù)程序員使用,二、想辦法讓現(xiàn)有的代碼和新技術(shù)兼容。 在兼容老代碼這一頭,我們已經(jīng)有了一些行動(dòng)。微軟在 Windows 7 中提供一個(gè)稱為用戶模式調(diào)度 (UMS) 的功能。UMS 可以將內(nèi)核模式的線程轉(zhuǎn)換為用戶模式線程,而應(yīng)用程序可以自己提供一個(gè) UMS 調(diào)度器來調(diào)度它們。這意味著,我們現(xiàn)在有機(jī)會(huì)重載掉系統(tǒng)調(diào)度器的默認(rèn)行為,而根據(jù)應(yīng)用自身的特點(diǎn)給出更合理的調(diào)度安排來。這個(gè)功能可以用在構(gòu)造更容易使用的并發(fā)模型上,這樣開發(fā)的模型可以與老代碼兼容(但 UMS 有一個(gè)讓人迷惑的限制:只能用在64bit 的Windows 7 版本上)。 同樣地,在推廣新技術(shù)方面,現(xiàn)在也有了很多成果。除了角色模型外,事務(wù)性內(nèi)存(這又是一種避免競爭條件,從而避免加鎖的方法)正在研究中;CSP 已經(jīng)有了數(shù)個(gè)實(shí)現(xiàn)(如由 Kent 大學(xué)開發(fā),針對 Java 的JCSP),同時(shí)還有針對 CSP 的模型檢證工具;至于 FP,最近因?yàn)槿藗冋J(rèn)為 Web 系統(tǒng)的建??梢栽诤瘮?shù)式編程范式中更好的表達(dá),F(xiàn)P 正在喚起人們的注意。我們?nèi)钡闹皇O滦录夹g(shù)的成功應(yīng)用范例(實(shí)際上,前面的技術(shù)并不是沒有成功范例,我們?nèi)钡氖墙?jīng)驗(yàn)?zāi)軌虼笠?guī)模運(yùn)用的范例 ),以及一支理解這些技術(shù)的程序員大軍了。對于這后一條,我甚至想,既然多線程編程唯一”容易”的事情是寫代碼,何不做出一種工具來讓程序員們可以用寫順序程序的思維來在這些新模型中編寫程序呢?這樣的工具會(huì)幫助程序員利用線性程序的思維來理解代碼,但是同時(shí)又讓人注意到自己的改動(dòng)正在影響系統(tǒng)的哪一部分。如果新模型的代碼變得好理解了,也許更多的人會(huì)使用它們。 (全文完) |
|