多線程是為了使得多個(gè)線程并行的工作以完成多項(xiàng)任務(wù),以提高系統(tǒng)的效率。線程是在同一時(shí)間需要完成多項(xiàng)任務(wù)的時(shí)候被實(shí)現(xiàn)的。
使用線程的好處有以下幾點(diǎn):
·使用線程可以把占據(jù)長時(shí)間的程序中的任務(wù)放到后臺去處理
·用戶界面可以更加吸引人,這樣比如用戶點(diǎn)擊了一個(gè)按鈕去觸發(fā)某些事件的處理,可以彈出一個(gè)進(jìn)度條來顯示處理的進(jìn)度
·程序的運(yùn)行速度可能加快
·在一些等待的任務(wù)實(shí)現(xiàn)上如用戶輸入、文件讀寫和網(wǎng)絡(luò)收發(fā)數(shù)據(jù)等,線程就比較游泳了。在這種情況下我們可以釋放一些珍貴的資源如內(nèi)存占用等等。
====
什么是多進(jìn)程:
進(jìn)程是程序在計(jì)算機(jī)上的一次執(zhí)行活動。當(dāng)你運(yùn)行一個(gè)程序,你就啟動了一個(gè)進(jìn)程。顯然,程序是死的(靜態(tài)的),進(jìn)程是活的(動態(tài)的)。進(jìn)程可以分為系統(tǒng)進(jìn)程和
用戶進(jìn)程。凡是用于完成操作系統(tǒng)的各種功能的進(jìn)程就是系統(tǒng)進(jìn)程,它們就是處于運(yùn)行狀態(tài)下的操作系統(tǒng)本身;用戶進(jìn)程就不必我多講了吧,所有由你啟動的進(jìn)程都
是用戶進(jìn)程。進(jìn)程是操作系統(tǒng)進(jìn)行資源分配的單位。
在Windows下,進(jìn)程又被細(xì)化為線程,也就是一個(gè)進(jìn)程下有多個(gè)能獨(dú)立運(yùn)行的更小的單位。
在同一個(gè)時(shí)間里,同一個(gè)計(jì)算機(jī)系統(tǒng)中如果允許兩個(gè)或兩個(gè)以上的進(jìn)程處于運(yùn)行狀態(tài),這便是多任務(wù)?,F(xiàn)代的操作系統(tǒng)幾乎都是多任務(wù)操作系統(tǒng),能夠同時(shí)管理多個(gè)進(jìn)
程的運(yùn)行。
多任務(wù)帶來的好處是明顯的,比如你可以邊聽mp3邊上網(wǎng),與此同時(shí)甚至可以將下載的文檔打印出來,而這些任務(wù)之間絲毫不會相互干擾。那么這里就涉及到并行
的問題,俗話說,一心不能二用,這對計(jì)算機(jī)也一樣,原則上一個(gè)CPU只能分配給一個(gè)進(jìn)程,以便運(yùn)行這個(gè)進(jìn)程。我們通常使用的計(jì)算機(jī)中只有一個(gè)CPU,也就
是說只有一顆心,要讓它一心多用,同時(shí)運(yùn)行多個(gè)進(jìn)程,就必須使用并發(fā)技術(shù)。實(shí)現(xiàn)并發(fā)技術(shù)相當(dāng)復(fù)雜,最容易理解的是“時(shí)間片輪轉(zhuǎn)進(jìn)程調(diào)度算法”,它的思想簡
單介紹如下:在操作系統(tǒng)的管理下,所有正在運(yùn)行的進(jìn)程輪流使用CPU,每個(gè)進(jìn)程允許占用CPU的時(shí)間非常短(比如10毫秒),這樣用戶根本感覺不出來
CPU是在輪流為多個(gè)進(jìn)程服務(wù),就好象所有的進(jìn)程都在不間斷地運(yùn)行一樣。但實(shí)際上在任何一個(gè)時(shí)間內(nèi)有且僅有一個(gè)進(jìn)程占有CPU。
如果一臺計(jì)算機(jī)有多個(gè)CPU,情況就不同了,如果進(jìn)程數(shù)小于CPU數(shù),則不同的進(jìn)程可以分配給不同的CPU來運(yùn)行,這樣,多個(gè)進(jìn)程就是真正同時(shí)運(yùn)行的,這便是并行。但如果進(jìn)程數(shù)大于CPU數(shù),則仍然需要使用并發(fā)技術(shù)。
在Windows中,進(jìn)行CPU分配是以線程為單位的,一個(gè)進(jìn)程可能由多個(gè)線程組成,這時(shí)情況更加復(fù)雜,但簡單地說,有如下關(guān)系:
總線程數(shù)<= CPU數(shù)量:并行運(yùn)行
總線程數(shù)> CPU數(shù)量:并發(fā)運(yùn)行
并行運(yùn)行的效率顯然高于并發(fā)運(yùn)行,所以在多CPU的計(jì)算機(jī)中,多任務(wù)的效率比較高。但是,如果在多CPU計(jì)算機(jī)中只運(yùn)行一個(gè)進(jìn)程(線程),就不能發(fā)揮多CPU的優(yōu)勢。
這里涉及到多任務(wù)操作系統(tǒng)的問題,多任務(wù)操作系統(tǒng)(如Windows)的基本原理是:操作系統(tǒng)將CPU的時(shí)間片分配給多個(gè)線程,每個(gè)線程在操作系統(tǒng)指定的時(shí)
間片內(nèi)完成(注意,這里的多個(gè)線程是分屬于不同進(jìn)程的).操作系統(tǒng)不斷的從一個(gè)線程的執(zhí)行切換到另一個(gè)線程的執(zhí)行,如此往復(fù),宏觀上看來,就好像是多個(gè)線
程在一起執(zhí)行.由于這多個(gè)線程分屬于不同的進(jìn)程,因此在我們看來,就好像是多個(gè)進(jìn)程在同時(shí)執(zhí)行,這樣就實(shí)現(xiàn)了多任務(wù).
question”始終困擾著哈姆雷特,對于“進(jìn)程還是線程?”這個(gè)問題,也經(jīng)常困擾著那些進(jìn)行軟件架構(gòu)設(shè)計(jì)的家伙。所以今天打算聊一下我對這個(gè)問題的體
會。假如你還搞不清楚線程和進(jìn)程的區(qū)別,請先找本操作系統(tǒng)原理的書好好拜讀一下,再回來看帖。
由于這個(gè)問題很容易引發(fā)口水戰(zhàn),事先聲明如下:多進(jìn)程和多線程,無法一概而論地說誰比誰好。因此本帖主要描述特定場景(與我所負(fù)責(zé)的產(chǎn)品相關(guān))下,進(jìn)程和線程的權(quán)衡經(jīng)驗(yàn),僅供大伙兒參考。
由于特定場景是本帖討論的前提,先說說我目前負(fù)責(zé)的產(chǎn)品的特點(diǎn):業(yè)務(wù)邏輯比較復(fù)雜、業(yè)務(wù)數(shù)據(jù)量比較大、對數(shù)據(jù)實(shí)時(shí)處理的性能要求比較高、對健壯性和安全性要求比較高、要求跨平臺(包括操作系統(tǒng)、數(shù)據(jù)庫)、某些情況下需要分布部署。
上面說了一大堆,其實(shí)有不少的應(yīng)用系統(tǒng)符合上述特點(diǎn),比如:某些網(wǎng)絡(luò)游戲服務(wù)器、某些金融行業(yè)的業(yè)務(wù)系統(tǒng)、某些電子商務(wù)的交易系統(tǒng)等等。如果你正在從事的是類似的應(yīng)用系統(tǒng)的設(shè)計(jì),希望我下面介紹的經(jīng)驗(yàn)對你有幫助。
進(jìn)程顆粒度問題
大伙兒應(yīng)該明白,進(jìn)程和線程都是處理并發(fā)(concurrency)的手段。對于上述這種比較復(fù)雜的系統(tǒng),如果你企圖全部用進(jìn)程(見注1)或者
全部用線程(見注2)來處理并發(fā),估計(jì)會死得很難看。所以,關(guān)鍵問題就是如何在進(jìn)程和線程之間進(jìn)行平衡(也就是確定進(jìn)程顆粒度的問題)。
我個(gè)人建議,盡量以業(yè)務(wù)邏輯的單元來劃分進(jìn)程。這樣做的好處有如下幾點(diǎn):
1、避免扯皮
一般來說,某個(gè)固定業(yè)務(wù)邏輯的開發(fā)人員也是相對固定的。如果業(yè)務(wù)邏輯對應(yīng)的某個(gè)進(jìn)程崩潰了,測試人員容易快速定位肇事者,然后直接提交Bug給他/她。
反之,一個(gè)進(jìn)程搞得太龐大,N多人摻和在里面,一旦進(jìn)程崩潰了,相關(guān)編程人員之間很容易互相扯皮,不利于維護(hù)安定團(tuán)結(jié)的局面;另外,由于測試人員經(jīng)常搞不清楚Bug屬于誰,經(jīng)常給錯(cuò)Bug,也容易制造人民內(nèi)部矛盾。
從上面可以看出來,相對細(xì)的進(jìn)程顆粒度能夠避免一些管理上的麻煩。由于XXX經(jīng)常教導(dǎo)我們:“穩(wěn)定壓倒一切”,所以該優(yōu)點(diǎn)列第一條。
2、健壯性、容錯(cuò)性
一般來說,開發(fā)人員的水平參差不齊,優(yōu)秀的畢竟是少數(shù)(具體參見“二八原理系列”的帖子)。所以難免會有菜鳥程序員搞出低級錯(cuò)誤,而有些低級錯(cuò)誤是致命的,會導(dǎo)致進(jìn)程的崩潰。
如果你是以業(yè)務(wù)邏輯劃分進(jìn)程,一個(gè)業(yè)務(wù)邏輯的進(jìn)程崩潰,對其它業(yè)務(wù)邏輯的影響不大(除非是該業(yè)務(wù)邏輯的依賴方);因此就不會出現(xiàn)“注2”提到的問題。
3、分布式
我常碰見的分布式部署需求,一般都是按照業(yè)務(wù)邏輯的維度來劃分。比如系統(tǒng)中有一個(gè)認(rèn)證模塊,里面包含有敏感的用戶認(rèn)證信息。這時(shí)候客戶就會要求把該模塊單獨(dú)部署在一臺經(jīng)過安全加固的主機(jī)中(以防階級敵人搞破壞)。
如果是以業(yè)務(wù)邏輯為單位劃分進(jìn)程,要滿足上述的部署需求就相對容易了(只要再配合恰當(dāng)?shù)倪M(jìn)程間通訊機(jī)制,下面會提到)。
另外,支持分布式部署還可以順帶解決性能問題。比如某個(gè)業(yè)務(wù)邏輯模塊特別消耗硬件資源(比如內(nèi)存、CPU、硬盤、帶寬),就可以把它拿出去單獨(dú)放一臺機(jī)器上跑。
4、跨編程語言
這個(gè)好處可能很多人容易忽略。一般來說,每個(gè)編程語言都有各自的優(yōu)缺點(diǎn)。如果你通過業(yè)務(wù)邏輯劃分進(jìn)程,就可以根據(jù)不同的業(yè)務(wù)邏輯的特點(diǎn)來選擇合適的編程語言。
比如:對于性能敏感的模塊,我就使用C++搞定;而對于一些業(yè)務(wù)邏輯密集型的模塊,則使用Java或Python開發(fā)。
進(jìn)程間通訊(以下簡稱IPC)問題
既然不可能把整個(gè)系統(tǒng)放入一個(gè)進(jìn)程,那就必然會碰到IPC的問題。下面就來說一下該如何選擇IPC。
各種操作系統(tǒng)里面,有很多稀奇古怪的IPC類型。由于要考慮跨平臺,首先砍掉一批(關(guān)于IPC的跨平臺問題,我在“跨平臺開發(fā)”系列中會提
到)。剩下的IPC類型中,能夠進(jìn)行數(shù)據(jù)傳輸?shù)腎PC就不多了,主要有如下幾種:套接字(以下簡稱Socket)、共享內(nèi)存、管道、文件。
其中Socket是我強(qiáng)烈推薦的IPC方式,理由如下:使用Socket可以天然地支持分布式部署;使用Socket可以比較容易地實(shí)現(xiàn)多種編
程語言的混合(比如C++、Java、Python、Flex都支持Socket);使用Socket還可以省掉了一大坨“鎖操作”的代碼。
列位看官中,或許有人在擔(dān)心Socket的性能問題,其實(shí)大可不必多慮。當(dāng)兩個(gè)進(jìn)程在本機(jī)上進(jìn)行Socket通訊時(shí),由于可以使用
localhost環(huán)回地址,數(shù)據(jù)不用經(jīng)過物理網(wǎng)卡,操作系統(tǒng)內(nèi)核還可以進(jìn)行某些優(yōu)化。這種情況下,Socket相對其它幾種IPC機(jī)制,不會有太大的性
能偏差。
最后再補(bǔ)充一下,Socket方式也可以有效防止扯皮問題。舉個(gè)例子:張三寫了一個(gè)進(jìn)程A,李四寫了一個(gè)進(jìn)程B,進(jìn)程A通過Socket方式發(fā)
數(shù)據(jù)給進(jìn)程B。突然有一天,兩個(gè)進(jìn)程的通訊出故障了。然后張三就說是李四接收數(shù)據(jù)出錯(cuò);李四就說張三發(fā)送數(shù)據(jù)出錯(cuò)。這時(shí)候怎么辦捏?很簡單,隨便找個(gè)
Sniffer軟件當(dāng)場抓一下數(shù)據(jù)包并Dump出來看,問題就水落石出了。
為啥還要線程?
上面說了這么多進(jìn)程的好處,有同學(xué)要問了:“那線程有什么用捏?”總的來說,使用線程出于兩方面的考慮:性能因素和編碼方便。
1、性能因素
由于某些操作系統(tǒng)(比如Windows)中的進(jìn)程比較重型,如果頻繁創(chuàng)建進(jìn)程或者創(chuàng)建大量進(jìn)程,會導(dǎo)致操作系統(tǒng)的負(fù)載過高。舉例如下:
假設(shè)你要開發(fā)一個(gè)類似Web Server的應(yīng)用。你針對每一個(gè)客戶端請求創(chuàng)建一個(gè)對應(yīng)的進(jìn)程用于進(jìn)行數(shù)據(jù)交互(是不是想起了古老的CGI :-)。一旦這個(gè)系統(tǒng)擴(kuò)容,用戶的并發(fā)連接數(shù)一增加,你的應(yīng)用立馬死翹翹。
上面的例子表明,跨平臺軟件系統(tǒng)的進(jìn)程數(shù)要保持相對穩(wěn)定。如果你的進(jìn)程數(shù)會隨著某些環(huán)境因素呈線性增長,那就相當(dāng)不妙了(順帶說一下,如果線程數(shù)會隨著環(huán)境因素呈線性增長,也相當(dāng)不妙)。而根據(jù)業(yè)務(wù)邏輯的單元劃分進(jìn)程,順便能達(dá)到“進(jìn)程數(shù)的相對穩(wěn)定”的效果。
2、編碼方面
由于業(yè)務(wù)邏輯內(nèi)部的數(shù)據(jù)耦合比較緊密。如果業(yè)務(wù)邏輯內(nèi)部的并發(fā)也用進(jìn)程來實(shí)現(xiàn),可能會導(dǎo)致大量的IPC編碼(任意兩個(gè)進(jìn)程之間只要有數(shù)據(jù)交互,就得寫一坨IPC代碼)。這或許會讓相關(guān)的編程人員怨聲載道。
當(dāng)然,編碼方面的問題也不是絕對的。假如你的系統(tǒng)有很成熟且方便易用的IPC庫,可以比較透明地封裝IPC相關(guān)操作,那這方面的問題也就不存在了。
關(guān)于多進(jìn)程和多線程,教科書上最經(jīng)典的一句話是“進(jìn)程是資源分配的最小單位,線程是CPU調(diào)度的最小單位”,這句話應(yīng)付考試基本上夠了,但如果在工作中遇到類似的選擇問題,那就沒有這么簡單了,選的不好,會讓你深受其害。
經(jīng)常在網(wǎng)絡(luò)上看到有的XDJM問“多進(jìn)程好還是多線程好?”、“Linux下用多進(jìn)程還是多線程?”等等期望一勞永逸的問題,我只能說:沒有最好,只有更好。根據(jù)實(shí)際情況來判斷,哪個(gè)更加合適就是哪個(gè)好。
我們按照多個(gè)不同的維度,來看看多線程和多進(jìn)程的對比(注:因?yàn)槭歉行缘谋容^,因此都是相對的,不是說一個(gè)好得不得了,另外一個(gè)差的無法忍受)。
看起來比較簡單,優(yōu)勢對比上是“線程 3.5 v 2.5 進(jìn)程”,我們只管選線程就是了?
呵呵,有這么簡單我就不用在這里浪費(fèi)口舌了,還是那句話,沒有絕對的好與壞,只有哪個(gè)更加合適的問題。我們來看實(shí)際應(yīng)用中究竟如何判斷更加合適。
1)需要頻繁創(chuàng)建銷毀的優(yōu)先用線程
原因請看上面的對比。
這種原則最常見的應(yīng)用就是Web服務(wù)器了,來一個(gè)連接建立一個(gè)線程,斷了就銷毀線程,要是用進(jìn)程,創(chuàng)建和銷毀的代價(jià)是很難承受的
2)需要進(jìn)行大量計(jì)算的優(yōu)先使用線程
所謂大量計(jì)算,當(dāng)然就是要耗費(fèi)很多CPU,切換頻繁了,這種情況下線程是最合適的。
這種原則最常見的是圖像處理、算法處理。
3)強(qiáng)相關(guān)的處理用線程,弱相關(guān)的處理用進(jìn)程
什么叫強(qiáng)相關(guān)、弱相關(guān)?理論上很難定義,給個(gè)簡單的例子就明白了。
一
般的Server需要完成如下任務(wù):消息收發(fā)、消息處理。“消息收發(fā)”和“消息處理”就是弱相關(guān)的任務(wù),而“消息處理”里面可能又分為“消息解碼”、“業(yè)
務(wù)處理”,這兩個(gè)任務(wù)相對來說相關(guān)性就要強(qiáng)多了。因此“消息收發(fā)”和“消息處理”可以分進(jìn)程設(shè)計(jì),“消息解碼”、“業(yè)務(wù)處理”可以分線程設(shè)計(jì)。
當(dāng)然這種劃分方式不是一成不變的,也可以根據(jù)實(shí)際情況進(jìn)行調(diào)整。
4)可能要擴(kuò)展到多機(jī)分布的用進(jìn)程,多核分布的用線程
原因請看上面對比。
5)都滿足需求的情況下,用你最熟悉、最拿手的方式
至于“數(shù)據(jù)共享、同步”、“編程、調(diào)試”、“可靠性”這幾個(gè)維度的所謂的“復(fù)雜、簡單”應(yīng)該怎么取舍,我只能說:沒有明確的選擇方法。但我可以告訴你一個(gè)選擇原則:如果多進(jìn)程和多線程都能夠滿足要求,那么選擇你最熟悉、最拿手的那個(gè)。
需要提醒的是:雖然我給了這么多的選擇原則,但實(shí)際應(yīng)用中基本上都是“進(jìn)程+線程”的結(jié)合方式,千萬不要真的陷入一種非此即彼的誤區(qū)。
轉(zhuǎn)自:http://blog.csdn.net/jw212/article/details/5928995