【IT168 技術文章】
就像莎士比亞的“To be, or not to be, that is the question”始終困擾著哈姆雷特,對於“進程還是線程?”這個問題,也經常困擾著那些進行軟體架構設計的傢伙。所以今天打算聊一下我對這個問題的體會。假如你還搞不清楚線程和進程的區別,請先找本作業系統原理的書好好拜讀一下,再回來看帖。
由於這個問題很容易引發口水戰,事先聲明如下:多進程和多線程,無法一概而論地說誰比誰好。因此本帖主要描述特定情境(與我所負責的產品相關)下,進程和線程的權衡經驗,僅供大伙兒參考。
由於特定情境是本帖討論的前提,先說說我目前負責的產品的特點:商務邏輯比較複雜、業務資料量比較大、對資料即時處理的效能要求比較高、對健壯性和安全性要求比較高、要求跨平台(包括作業系統、資料庫)、某些情況下需要分布部署。
上面說了一大堆,其實有不少的應用系統符合上述特點,比如:某些網路遊戲伺服器、某些金融行業的業務系統、某些電子商務的交易系統等等。如果你正在從事的是類似的應用系統的設計,希望我下面介紹的經驗對你有協助。
進程顆粒度問題
大伙兒應該明白,進程和線程都是處理並發(concurrency)的手段。對於上述這種比較複雜的系統,如果你企圖全部用進程(見注1)或者全部用線程(見注2)來處理並發,估計會死得很難看。所以,關鍵問題就是如何在進程和線程之間進行平衡(也就是確定進程顆粒度的問題)。
我個人建議,盡量以商務邏輯的單元來劃分進程。這樣做的好處有如下幾點:
1、避免扯皮
一般來說,某個固定商務邏輯的開發人員也是相對固定的。如果商務邏輯對應的某個進程崩潰了,測試人員容易快速定位肇事者,然後直接提交Bug給他/她。
反之,一個進程搞得太龐大,N多人摻和在裡面,一旦進程崩潰了,相關編程人員之間很容易互相扯皮,不利於維護安定團結的局面;另外,由於測試人員經常搞不清楚Bug屬於誰,經常給錯Bug,也容易製造人民內部矛盾。
從上面可以看出來,相對細的進程顆粒度能夠避免一些管理上的麻煩。由於XXX經常教導我們:“穩定壓倒一切”,所以該優點列第一條。
2、健壯性、容錯性
一般來說,開發人員的水平參差不齊,優秀的畢竟是少數(具體參見“二八原理系列”的文章)。所以難免會有菜鳥程式員搞出低級錯誤,而有些低級錯誤是致命的,會導致進程的崩潰。
如果你是以商務邏輯劃分進程,一個商務邏輯的進程崩潰,對其它商務邏輯的影響不大(除非是該商務邏輯的依賴方);因此就不會出現“注2”提到的問題。
3、分布式
我常碰見的分布式部署需求,一般都是按照商務邏輯的維度來劃分。比如系統中有一個認證模組,裡麵包含有敏感的使用者認證資訊。這時候客戶就會要求把該模組單獨部署在一台經過安全強化的主機中(以防階級敵人搞破壞)。
如果是以商務邏輯為單位劃分進程,要滿足上述的部署需求就相對容易了(只要再配合恰當的進程間通訊機制,下面會提到)。
另外,支援分布式部署還可以順帶解決效能問題。比如某個商務邏輯模組特別消耗硬體資源(比如記憶體、CPU、硬碟、頻寬),就可以把它拿出去單獨放一台機器上跑。
4、跨程式設計語言
這個好處可能很多人容易忽略。一般來說,每個程式設計語言都有各自的優缺點。如果你通過商務邏輯劃分進程,就可以根據不同的商務邏輯的特點來選擇合適的程式設計語言。
比如:對於效能敏感的模組,我就使用C++搞定;而對於一些商務邏輯密集型的模組,則使用Java或Python開發。
進程間通訊(以下簡稱IPC)問題
既然不可能把整個系統放入一個進程,那就必然會碰到IPC的問題。下面就來說一下該如何選擇IPC。
各種作業系統裡面,有很多稀奇古怪的IPC類型。由於要考慮跨平台,首先砍掉一批(關於IPC的跨平台問題,我在“跨平台開發”系列中會提到)。剩下的 IPC類型中,能夠進行資料轉送的IPC就不多了,主要有如下幾種:通訊端(以下簡稱Socket)、共用記憶體、管道、檔案。
其中Socket是我強烈推薦的IPC方式,理由如下:使用Socket可以天然地支援分布式部署;使用Socket可以比較容易地實現多種程式設計語言的混合(比如C++、Java、Python、Flex都支援Socket);使用Socket還可以省掉了一大坨“鎖操作”的代碼。
列位看官中,或許有人在擔心Socket的效能問題,其實大可不必多慮。當兩個進程在本機上進行Socket通訊時,由於可以使用localhost迴路位址,資料不用經過物理網卡,作業系統核心還可以進行某些最佳化。這種情況下,Socket相對其它幾種IPC機制,不會有太大的效能偏差。
最後再補充一下,Socket方式也可以有效防止扯皮問題。舉個例子:張三寫了一個進程A,李四寫了一個進程B,進程A通過Socket方式發資料給進程 B。突然有一天,兩個進程的通訊出故障了。然後張三就說是李四接收資料出錯;李四就說張三發送資料出錯。這時候怎麼辦捏?很簡單,隨便找個Sniffer 軟體當場抓一下資料包並Dump出來看,問題就水落石出了。
為啥還要線程?
上面說了這麼多進程的好處,有同學要問了:“那線程有什麼用捏?”總的來說,使用線程出於兩方面的考慮:效能因素和編碼方便。
1、效能因素
由於某些作業系統(比如Windows)中的進程比較重型,如果頻繁建立進程或者建立大量進程,會導致作業系統的負載過高。舉例如下:
假設你要開發一個類似Web Server的應用。你針對每一個用戶端請求建立一個對應的進程用於進行資料互動(是不是想起了古老的CGI :-)。一旦這個系統擴容,使用者的並發串連數一增加,你的應用立馬死翹翹。
上面的例子表明,跨平台軟體系統的進程數要保持相對穩定。如果你的進程數會隨著某些環境因素呈線性增長,那就相當不妙了(順帶說一下,如果線程數會隨著環境因素呈線性增長,也相當不妙)。而根據商務邏輯的單元劃分進程,順便能達到“進程數的相對穩定”的效果。
2、編碼方面
由於商務邏輯內部的資料耦合比較緊密。如果商務邏輯內部的並發也用進程來實現,可能會導致大量的IPC編碼(任意兩個進程之間只要有資料互動,就得寫一坨IPC代碼)。這或許會讓相關的編程人員怨聲載道。
當然,編碼方面的問題也不是絕對的。假如你的系統有很成熟且方便易用的IPC庫,可以比較透明地封裝IPC相關操作,那這方面的問題也就不存在了。
Ref:http://tech.it168.com/a2009/0413/271/000000271998.shtml