多線程和多進程模型的選用
這裏的線程指通過Linux的pthread_create而產生的原生線程,線程資源很寶貴,能被操作系統的任務調度器看見的(不是Python gevent、Gogorouine裏的概念);
我們討論以下兩種模型;
- 多進程單線程模型(以下簡稱爲多進程);
- 單進程多線程模型(以下簡稱爲多線程);
多進程模型
優點
編程相對容易;通常不需要考慮鎖和同步資源的問題。
更強的容錯性:比起多線程的一個好處是一個進程崩潰了不會影響其他進程。
有內核保證的隔離:數據和錯誤隔離。
對於使用如C/C++這些語言編寫的本地代碼,錯誤隔離是非常有用的:採用多進程架構的程序一般可以做到一定程度的自恢復;(master守護進程監控所有worker進程,發現進程掛掉後將其重啓)
多進程的案例
nginx主流的工作模式是多進程模式(也支持多線程模型)
幾乎所有的web server服務器服務都有多進程的,至少有一個守護進程配合一個worker進程,例如apached,httpd等等以d結尾的進程包括init.d本身就是0級總進程,所有你認知的進程都是它的子進程;
chrome瀏覽器也是多進程方式。
Redis也可以歸類到“多進程單線程”模型(平時工作是單個進程,涉及到耗時操作如持久化或aof重寫時會用到多個進程)
多線程模型
優點
多線程優點:創建速度快,方便高效的數據共享
共享數據:多線程間可以共享同一虛擬地址空間;多進程間的數據共享就需要用到共享內存、信號量等IPC技術;
較輕的上下文切換開銷 - 不用切換地址空間,不用更改寄存器,不用刷新TLB。
提供非均質的服務
如果全都是計算任務,但每個任務的耗時不都爲1s,而是1ms-1s之間波動;這樣,多線程相比多進程的優勢就體現出來,它能有效降低“簡單任務被複雜任務壓住”的概率;
適用的場景
1 線程間有數據共享,並且數據是需要修改的(不同任務間需要大量共享數據或頻繁通信時);
2 提供非均質的服務(有優先級任務處理)事件響應有優先級;
3 單任務並行計算,在非CPU Bound的場景下提高響應速度,降低時延;
4 與人有IO交互的應用,良好的用戶體驗(鍵盤鼠標的輸入,立刻響應)
多線程案例
桌面軟件,響應用戶輸入的是一個線程,後臺程序處理是另外的線程;
memcached
選用
單進程多線程和多進程單線程,2種模式如何取捨?
進程線程間創建的開銷不足作爲選擇的依據,因爲一般我們都是使用線程池或者進程池,在系統啓動時就創建了固定的線程或進程,不會頻繁的創建和銷燬;
首先,根據工作集(需要共享的內存)的大小來定;如果工作集較大,就用多線程,避免cpu cache頻繁的換入換出;比如memcached緩存系統;
其次,選擇的依據根據以上多線程適用的場景來對比自身的業務場景,是否有這樣場景需求:數據共享、提供非均質的服務,單任務拆散並行化等;
如果沒有必要,或者多進程就可以很好的勝任,就多用多進程,享受單線程編程帶來便利;
RCU的發明者,Paul McKenny 在《Is Parallel Programming Hard, And, If So, What Can You Do About It?》說過:
能用多進程方便的解決問題的時候不要使用多線程。
參考
ref:《Linux多線程服務端編程:使用muduo網絡庫》
ref:http://www.zhihu.com/question/19903801
ref:https://computing.llnl.gov/tutorials/pthreads/#WhyPthreads
Posted by: 大CC | 10OCT,2015
博客:blog.me115.com [訂閱]
Github:大CC
----------------------------------------------------------------------------------------------------------
淺談多進程多線程的選擇(轉)
關於多進程和多線程,教科書上最經典的一句話是“進程是資源分配的最小單位,線程是CPU調度的最小單位”,這句話應付考試基本上夠了,但如果在工作中遇到類似的選擇問題,那就沒有這麼簡單了,選的不好,會讓你深受其害。
經常在網絡上看到有的XDJM問“多進程好還是多線程好?”、“Linux下用多進程還是多線程?”等等期望一勞永逸的問題,我只能說:沒有最好,只有更好。根據實際情況來判斷,哪個更加合適就是哪個好。
我們按照多個不同的維度,來看看多線程和多進程的對比(注:因爲是感性的比較,因此都是相對的,不是說一個好得不得了,另外一個差的無法忍受)。
對比維度 |
多進程 |
多線程 |
總結 |
數據共享、同步 |
數據共享複雜,需要用IPC;數據是分開的,同步簡單 |
因爲共享進程數據,數據共享簡單,但也是因爲這個原因導致同步複雜 |
各有優勢 |
內存、CPU |
佔用內存多,切換複雜,CPU利用率低 |
佔用內存少,切換簡單,CPU利用率高 |
線程佔優 |
創建銷燬、切換 |
創建銷燬、切換複雜,速度慢 |
創建銷燬、切換簡單,速度很快 |
線程佔優 |
編程、調試 |
編程簡單,調試簡單 |
編程複雜,調試複雜 |
進程佔優 |
可靠性 |
進程間不會互相影響 |
一個線程掛掉將導致整個進程掛掉 |
進程佔優 |
分佈式 |
適應於多核、多機分佈式;如果一臺機器不夠,擴展到多臺機器比較簡單 |
適應於多核分佈式 |
進程佔優 |
看起來比較簡單,優勢對比上是“線程 3.5 v 2.5 進程”,我們只管選線程就是了?
呵呵,有這麼簡單我就不用在這裏浪費口舌了,還是那句話,沒有絕對的好與壞,只有哪個更加合適的問題。我們來看實際應用中究竟如何判斷更加合適。
1)需要頻繁創建銷燬的優先用線程
原因請看上面的對比。
這種原則最常見的應用就是Web服務器了,來一個連接建立一個線程,斷了就銷燬線程,要是用進程,創建和銷燬的代價是很難承受的
2)需要進行大量計算的優先使用線程
所謂大量計算,當然就是要耗費很多CPU,切換頻繁了,這種情況下線程是最合適的。
這種原則最常見的是圖像處理、算法處理。
3)強相關的處理用線程,弱相關的處理用進程
什麼叫強相關、弱相關?理論上很難定義,給個簡單的例子就明白了。
一般的Server需 要完成如下任務:消息收發、消息處理。“消息收發”和“消息處理”就是弱相關的任務,而“消息處理”裏面可能又分爲“消息解碼”、“業務處理”,這兩個任 務相對來說相關性就要強多了。因此“消息收發”和“消息處理”可以分進程設計,“消息解碼”、“業務處理”可以分線程設計。
當然這種劃分方式不是一成不變的,也可以根據實際情況進行調整。
4)可能要擴展到多機分佈的用進程,多核分佈的用線程
原因請看上面對比。
5)都滿足需求的情況下,用你最熟悉、最拿手的方式
至於“數據共享、同步”、“編程、調試”、“可靠性”這幾個維度的所謂的“複雜、簡單”應該怎麼取捨,我只能說:沒有明確的選擇方法。但我可以告訴你一個選擇原則:如果多進程和多線程都能夠滿足要求,那麼選擇你最熟悉、最拿手的那個。
需要提醒的是:雖然我給了這麼多的選擇原則,但實際應用中基本上都是“進程+線程”的結合方式,千萬不要真的陷入一種非此即彼的誤區。
1、多進程與多線程的簡單比較
- 多進程
優點:內存隔離,單個進程的異常不會導致整個應用的崩潰。方便測試,編程簡單。
缺點:進程間調用,通訊和切換均比多線程大,耗資源。
使用場所:目標子動能交互少,如果資源和性能許可,可以設計由多個子應用程序來組合完成目的。
- 多線程
優點:提高系統的並行性,並且開銷小。數據共享方便(不需要進程間的通信)
缺點:沒有內存隔離,單個現成的崩潰會導致整個應用程序的退出,發生採內存等bug時,定位及其不方便。編程複雜;調試困難;線程執行的隨機性可能導致邏輯混亂,甚至發生死鎖現象;
使用場所:在存在大量IO,網絡等耗時操作,或者需要和用戶交互時,使用多線程有利於提高系統的並行性和用戶界面快速響應從而提高友好性。
2、設計時應注意的事項
- 儘量避免長駐內存的進程,例如那些很少用到的功能,或週期性很長(10分鐘以上),
把它們的功能提取出來,做成一個小的應用程序。需要的時候再把它們拉起來(如通過crontab配置,或直接system)。
- 把目標設計成子功能系統的組合可用提高重用的易用性和維護性。
把目標根據功能劃分不同的子系統,子系統間遵循特定的協議(文本或XML),由通訊聯繫起來,協作完成目標。
典型的案例就是UNIX或LINUX的工具使用。如:$ cat veglist fruitlist | sort > clist,用cat打開文件,協議是字符流,通過管道(通訊手段)傳給sort進行排序,把排序的結果流重定向到文件中。這種自由組合協作風格應用思想和編程思路也是吸引衆多UNIX擁護者的原因之一吧。
在軟件設計中,選擇多進程還是多線程還跟很多因素有關,例如對數據實時處理的性能要求、對健壯性和安全性要求、是否要求跨平臺(包括操作系統和數據庫)及是否需要分佈部署。因此,在設計中應充分考慮各種因素以求做到最大利用系統資源。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
做unix上server程序實現時:到底是該選擇多線程併發模型還是多線程併發模型呢?
想到這個問題是源於閱讀scgi,nginx,memched的源碼~如下:
scgi的實現是用的多進程,主進程負責監聽socket連接請求,然後分發給各個子進程來處理。
nginx的實現是用的多進程,創建好子進程之後,各個子進程直接自己來監聽socket連接請求並處理。
memched的實現則是用的多線程,主線程負責監聽請求,然後分發給各個子線程來處理。
那這樣的話:本質上是兩個可選條件的組合
1.多線程 還是 多進程?
2.是master統一監聽請求並分發給worker處理 還是 各個worker自己監聽請求並處理?
其中第2點的區別產生源於:
是 先創建socket 再fork子進程 還是 先fork子進程再創建socket?
前者因爲先創建的socket,所以fork出來的子進程自然也繼承了這個東西,所以可以直接用它來監聽請求。 後者則不行···
================================================
先說第2點的區別吧:
我覺得如果說:讓各個worker自己來監聽請求的話:因爲其監聽的都是同一個socket,所以會產生競爭!到底誰先accept? 所以在實現上也搞個accept鎖,讓各個進程來爭奪這個。否則來一個請求,所有子進程都唄warkup的話···驚羣現象發生了·······
那master來統一監聽並分發呢?嗯,master得知道各個worker是否就緒可用··怎麼知道?好吧··各種IPC進程間通信····貌似也要有不小的開銷···
======================================================
那和2點都該怎樣來抉擇 或者說 都有什麼優缺點 適用用什麼場景呢?
問題一. 選擇多線程 還是 多進程
我理解這個問題,其實本質上就一句話:多進程之間各自有用自己的內存空間,而同一個進程內的多線程之間共享內存空間。
首先:server程序的本質是:client來獲取和修改數據. 那麼:不管是nginx還是memcached都是爲了解決這個問題而存在的。
對於memcached而言(單進程多線程):這個進程啓動後,用戶寫入這個cache的所有內容都是寫在這個進程的堆空間中。 也就是說:所有的數據都是集中存在於唯一一個進程空間中,這樣各個線程完全可以共享這些數據,可以全部的去查詢和修改; 而如果我們換用多進程來實現的話,那用戶傳入cache的數據都存在哪?傳過來後是一個進程來處理的,那處理結果必然存在這個進程自己的內存空間中,這樣別的進程想讀取的話怎麼辦?IPC?消耗多大! 正是因爲多進程各自的內存空間是獨立的,導致傳入的數據也是相互獨立的,而不是存放在一起的!這樣想跨進程查詢的話就得IPC,而且及其頻繁,而且還不知道是該查哪個子進程的空間!所以這種方式明擺着不合適,那如果我們是各個子進程獲取到用戶傳入的數據之後:不是保存在自己的進程中,而是還要寫入到一個多進程共享的內存中,這樣好使得數據變爲全局來避免IPC呢?這種實現的話就相當於:我們將每個進程傳入的數據都寫入到進程共享內存中!而各個進程只要操作這個共享內存就可以了。這個實現當然可以,沒什麼問題,但是顯然沒有直接單進程多線程來的easy,這樣將所有數據直接放在進程空間中,而非共享內存中!操作起來也速度,簡單!
所以從上邊的角度來將:之所以選擇多線程是基於全局數據的考慮,各個處理請求的進程/線程 都要全局的讀取,而不是各自空間內讀取!!
那因爲多個處理進程/線程是同時讀寫同一份大數據,必然牽扯到同步的問題,避免同時寫之類的操作。所以還加一些鎖。
而MySQL的實現呢?也是用的多線程,按照上邊的原因也很容易解釋的通。各個請求到來之後:需要各個處理worker來操作全局數據,所以要求這些workers都能訪問同一片數據。所以多線程是中很合適的方式,大家都處於同一個進程的內存空間中! 當然:也牽扯讀寫鎖的問題來同步等~~
=============================================================================================================
那nginx呢?爲什麼用的都是多進程??
其實這裏一句話就可以解釋:其只是讀取server腳本,不會更改。
請求到達nginx後,其如果不用cgi協議的話,就是直接啓動多個進程來調用後端server腳本並運行之。 所以:nginx只負責讀取server腳本,比如一個PHP腳本.
其是一個讀取,不牽扯寫入/更新··
那什麼感覺nginx也會寫入數據呢?其實這裏寫入數據是寫入到數據庫中,php/python腳本僅僅是加在中間的一個處理層·,這一層僅僅是個媒介!!!沒有具體的數據寫入
所以一個寫請求到達nginx之後:其要做的工作就是先讀php腳本,而後php腳本再調用mysql來寫全局數據庫!那這樣的話:nginx僅僅是讀腳本,不牽扯任何寫!!Php腳本僅僅是nginx的一個媒介而已~~藉助php來操作數據! 所以真正的讀寫數據都是在後端的Mysql數據庫這裏,在這裏就使用多線程來操作全局數據(memcache的全局數據存在內存,而Mysql的全局數據存在硬盤而已)~~而nginx僅僅是讀取php腳本,而且各個php腳本都是相互獨立的,所以讀取時不需要同步之類的,所以這裏用多線程和多進程都可以做到!!那爲毛用多進程呢?因爲:多進程的話各個進程之間相互獨立,一個crash不會影響其他進程;而多線程:任何一個子線程crash了,其他子線程就全部死掉了!這意味着:使用多線程的話:各個用戶的請求處理是相互影響的,一個掛了,其他人的請求也跟着悲劇;而多進程就不會相互影響! 所以更適合使用多進程!
亦即:我覺得nginx使用多進程和多線程在其他方面沒什麼區別,最大考慮的因素是相互影響的問題:爲了使各個請求處理不相互影響,從而最終選擇多進程!!
===================================================================================================================
總結一下:我認爲牽扯到全局數據寫入/更新的操作,就用多線程;否則就多進程提高健壯性吧。
-----------------------------------------------------------------------------------------------------------------------------------------------
進程池與線程池
自己胡亂寫一點理解,以作記錄,沒有邏輯,沒有主題,沒有結論,歡迎拍拍。
這兩種模式在服務器開發中還是用得比較多。技術本無所謂好壞,不同技術都有自己的特點和缺點,只有他們的適用場合不同而已。進程池的最大好處在於任務的隔離性,不同進程處理的任務之間是相互獨立,互不影響的,即使某個進程再處理某個用戶的某個請求是crash了,也不會影響整個程序的執行。而線程池則正好相反,某個現場崩潰了,其他線程也不會有好果子吃,大家一起歸西!很多人選擇進程池就是因爲這個原因,但是也不意味着進程池裏面就可以隨便crash了,作爲服務器程序員,不斷追求完美,處理所以可能和不可能發生的異常情況,精雕細琢內存的使用本來就是份內事.
線程池的優點之一就是現場切換代價小,而且數據共享容易(這也可以說成是它的缺點),處理重複的小任務還是比較好的。
市面上有些進程池框架是對每個用戶連接一個進程進行處理,這種方式下併發量恐怕上不來(如果你的應用本來就不要球併發就沒的說了),這樣每個用戶就和一個特定進程關聯了。銀行系統裏面應該用得比較多,他們不缺錢,他們要儘可能杜絕用戶之間相互影響。如果有一個主進程來處理解析協議和業務邏輯,每個進程只是作爲工作進程,從隊列裏面取任務進行處理,如果進程過多,切換開銷會很客觀,但是如果你本來就是多核的服務器,而且進程數很少,且在這樣的配置下,你的業務運行得很好,那也沒的說了。