就像莎士比亞的“To be, or not to be, that is the
question”始終困擾著哈姆雷特,對于“進程還是線程?”這個問題,也經(jīng)常困擾著那些進行軟件架構(gòu)設計的家伙。所以今天打算聊一下我對這個問題的體
會。假如你還搞不清楚線程和進程的區(qū)別,請先找本操作系統(tǒng)原理的書好好拜讀一下,再回來看帖。
由于這個問題很容易引發(fā)口水戰(zhàn),事先聲明如下:多進程和多線程,無法一概而論地說誰比誰好。因此本帖主要描述特定場景(與我所負責的產(chǎn)品相關)下,進程和線程的權(quán)衡經(jīng)驗,僅供大伙兒參考。
由于特定場景是本帖討論的前提,先說說我目前負責的產(chǎn)品的特點:業(yè)務邏輯比較復雜、業(yè)務數(shù)據(jù)量比較大、對數(shù)據(jù)實時處理的性能要求比較高、對健壯性和安全性要求比較高、要求跨平臺(包括操作系統(tǒng)、數(shù)據(jù)庫)、某些情況下需要分布部署。
上面說了一大堆,其實有不少的應用系統(tǒng)符合上述特點,比如:某些網(wǎng)絡游戲服務器、某些金融行業(yè)的業(yè)務系統(tǒng)、某些電子商務的交易系統(tǒng)等等。如果你正在從事的是類似的應用系統(tǒng)的設計,希望我下面介紹的經(jīng)驗對你有幫助。
進程顆粒度問題
大伙兒應該明白,進程和線程都是處理并發(fā)(concurrency)的手段。對于上述這種比較復雜的系統(tǒng),如果你企圖全部用進程(見注1)或者
全部用線程(見注2)來處理并發(fā),估計會死得很難看。所以,關鍵問題就是如何在進程和線程之間進行平衡(也就是確定進程顆粒度的問題)。
我個人建議,盡量以業(yè)務邏輯的單元來劃分進程。這樣做的好處有如下幾點:
1、避免扯皮
一般來說,某個固定業(yè)務邏輯的開發(fā)人員也是相對固定的。如果業(yè)務邏輯對應的某個進程崩潰了,測試人員容易快速定位肇事者,然后直接提交Bug給他/她。
反之,一個進程搞得太龐大,N多人摻和在里面,一旦進程崩潰了,相關編程人員之間很容易互相扯皮,不利于維護安定團結(jié)的局面;另外,由于測試人員經(jīng)常搞不清楚Bug屬于誰,經(jīng)常給錯Bug,也容易制造人民內(nèi)部矛盾。
從上面可以看出來,相對細的進程顆粒度能夠避免一些管理上的麻煩。由于XXX經(jīng)常教導我們:“穩(wěn)定壓倒一切”,所以該優(yōu)點列第一條。
2、健壯性、容錯性
一般來說,開發(fā)人員的水平參差不齊,優(yōu)秀的畢竟是少數(shù)(具體參見“二八原理系列”的帖子)。所以難免會有菜鳥程序員搞出低級錯誤,而有些低級錯誤是致命的,會導致進程的崩潰。
如果你是以業(yè)務邏輯劃分進程,一個業(yè)務邏輯的進程崩潰,對其它業(yè)務邏輯的影響不大(除非是該業(yè)務邏輯的依賴方);因此就不會出現(xiàn)“注2”提到的問題。
3、分布式
我常碰見的分布式部署需求,一般都是按照業(yè)務邏輯的維度來劃分。比如系統(tǒng)中有一個認證模塊,里面包含有敏感的用戶認證信息。這時候客戶就會要求把該模塊單獨部署在一臺經(jīng)過安全加固的主機中(以防階級敵人搞破壞)。
如果是以業(yè)務邏輯為單位劃分進程,要滿足上述的部署需求就相對容易了(只要再配合恰當?shù)倪M程間通訊機制,下面會提到)。
另外,支持分布式部署還可以順帶解決性能問題。比如某個業(yè)務邏輯模塊特別消耗硬件資源(比如內(nèi)存、CPU、硬盤、帶寬),就可以把它拿出去單獨放一臺機器上跑。
4、跨編程語言
這個好處可能很多人容易忽略。一般來說,每個編程語言都有各自的優(yōu)缺點。如果你通過業(yè)務邏輯劃分進程,就可以根據(jù)不同的業(yè)務邏輯的特點來選擇合適的編程語言。
比如:對于性能敏感的模塊,我就使用C++搞定;而對于一些業(yè)務邏輯密集型的模塊,則使用Java或Python開發(fā)。
進程間通訊(以下簡稱IPC)問題
既然不可能把整個系統(tǒng)放入一個進程,那就必然會碰到IPC的問題。下面就來說一下該如何選擇IPC。
各種操作系統(tǒng)里面,有很多稀奇古怪的IPC類型。由于要考慮跨平臺,首先砍掉一批(關于IPC的跨平臺問題,我在“跨平臺開發(fā)”系列中會提
到)。剩下的IPC類型中,能夠進行數(shù)據(jù)傳輸?shù)腎PC就不多了,主要有如下幾種:套接字(以下簡稱Socket)、共享內(nèi)存、管道、文件。
其中Socket是我強烈推薦的IPC方式,理由如下:使用Socket可以天然地支持分布式部署;使用Socket可以比較容易地實現(xiàn)多種編
程語言的混合(比如C++、Java、Python、Flex都支持Socket);使用Socket還可以省掉了一大坨“鎖操作”的代碼。
列位看官中,或許有人在擔心Socket的性能問題,其實大可不必多慮。當兩個進程在本機上進行Socket通訊時,由于可以使用
localhost環(huán)回地址,數(shù)據(jù)不用經(jīng)過物理網(wǎng)卡,操作系統(tǒng)內(nèi)核還可以進行某些優(yōu)化。這種情況下,Socket相對其它幾種IPC機制,不會有太大的性
能偏差。
最后再補充一下,Socket方式也可以有效防止扯皮問題。舉個例子:張三寫了一個進程A,李四寫了一個進程B,進程A通過Socket方式發(fā)
數(shù)據(jù)給進程B。突然有一天,兩個進程的通訊出故障了。然后張三就說是李四接收數(shù)據(jù)出錯;李四就說張三發(fā)送數(shù)據(jù)出錯。這時候怎么辦捏?很簡單,隨便找個
Sniffer軟件當場抓一下數(shù)據(jù)包并Dump出來看,問題就水落石出了。
為啥還要線程?
上面說了這么多進程的好處,有同學要問了:“那線程有什么用捏?”總的來說,使用線程出于兩方面的考慮:性能因素和編碼方便。
1、性能因素
由于某些操作系統(tǒng)(比如Windows)中的進程比較重型,如果頻繁創(chuàng)建進程或者創(chuàng)建大量進程,會導致操作系統(tǒng)的負載過高。舉例如下:
假設你要開發(fā)一個類似Web Server的應用。你針對每一個客戶端請求創(chuàng)建一個對應的進程用于進行數(shù)據(jù)交互(是不是想起了古老的CGI :-)。一旦這個系統(tǒng)擴容,用戶的并發(fā)連接數(shù)一增加,你的應用立馬死翹翹。
上面的例子表明,跨平臺軟件系統(tǒng)的進程數(shù)要保持相對穩(wěn)定。如果你的進程數(shù)會隨著某些環(huán)境因素呈線性增長,那就相當不妙了(順帶說一下,如果線程數(shù)會隨著環(huán)境因素呈線性增長,也相當不妙)。而根據(jù)業(yè)務邏輯的單元劃分進程,順便能達到“進程數(shù)的相對穩(wěn)定”的效果。
2、編碼方面
由于業(yè)務邏輯內(nèi)部的數(shù)據(jù)耦合比較緊密。如果業(yè)務邏輯內(nèi)部的并發(fā)也用進程來實現(xiàn),可能會導致大量的IPC編碼(任意兩個進程之間只要有數(shù)據(jù)交互,就得寫一坨IPC代碼)。這或許會讓相關的編程人員怨聲載道。
當然,編碼方面的問題也不是絕對的。假如你的系統(tǒng)有很成熟且方便易用的IPC庫,可以比較透明地封裝IPC相關操作,那這方面的問題也就不存在了。
魚還是熊掌:淺談多進程多線程的選擇
關于多進程和多線程,教科書上最經(jīng)典的一句話是“進程是資源分配的最小單位,線程是CPU調(diào)度的最小單位”,這句話應付考試基本上夠了,但如果在工作中遇到類似的選擇問題,那就沒有這么簡單了,選的不好,會讓你深受其害。
經(jīng)常在網(wǎng)絡上看到有的XDJM問“多進程好還是多線程好?”、“Linux下用多進程還是多線程?”等等期望一勞永逸的問題,我只能說:沒有最好,只有更好。根據(jù)實際情況來判斷,哪個更加合適就是哪個好。
我們按照多個不同的維度,來看看多線程和多進程的對比(注:因為是感性的比較,因此都是相對的,不是說一個好得不得了,另外一個差的無法忍受)。

看起來比較簡單,優(yōu)勢對比上是“線程 3.5 v 2.5 進程”,我們只管選線程就是了?
呵呵,有這么簡單我就不用在這里浪費口舌了,還是那句話,沒有絕對的好與壞,只有哪個更加合適的問題。我們來看實際應用中究竟如何判斷更加合適。
1)需要頻繁創(chuàng)建銷毀的優(yōu)先用線程
原因請看上面的對比。
這種原則最常見的應用就是Web服務器了,來一個連接建立一個線程,斷了就銷毀線程,要是用進程,創(chuàng)建和銷毀的代價是很難承受的
2)需要進行大量計算的優(yōu)先使用線程
所謂大量計算,當然就是要耗費很多CPU,切換頻繁了,這種情況下線程是最合適的。
這種原則最常見的是圖像處理、算法處理。
3)強相關的處理用線程,弱相關的處理用進程
什么叫強相關、弱相關?理論上很難定義,給個簡單的例子就明白了。
一
般的Server需要完成如下任務:消息收發(fā)、消息處理。“消息收發(fā)”和“消息處理”就是弱相關的任務,而“消息處理”里面可能又分為“消息解碼”、“業(yè)
務處理”,這兩個任務相對來說相關性就要強多了。因此“消息收發(fā)”和“消息處理”可以分進程設計,“消息解碼”、“業(yè)務處理”可以分線程設計。
當然這種劃分方式不是一成不變的,也可以根據(jù)實際情況進行調(diào)整。
4)可能要擴展到多機分布的用進程,多核分布的用線程
原因請看上面對比。
5)都滿足需求的情況下,用你最熟悉、最拿手的方式
至于“數(shù)據(jù)共享、同步”、“編程、調(diào)試”、“可靠性”這幾個維度的所謂的“復雜、簡單”應該怎么取舍,我只能說:沒有明確的選擇方法。但我可以告訴你一個選擇原則:如果多進程和多線程都能夠滿足要求,那么選擇你最熟悉、最拿手的那個。
需要提醒的是:雖然我給了這么多的選擇原則,但實際應用中基本上都是“進程+線程”的結(jié)合方式,千萬不要真的陷入一種非此即彼的誤區(qū)。
轉(zhuǎn)自:http://blog.csdn.net/jw212/article/details/5928995