在學習Python多進程、多線程以前,先腦補一下以下場景;html
說有這麼一道題:小紅燒水須要10分鐘,拖地須要5分鐘,洗菜須要5分鐘,第一種方式:若是同樣同樣去幹,就是簡單的加法,所有作完,須要20分鐘;第二種方式:若是在燒水的同時去拖地、洗菜,所有作完,只須要10分鐘!也可類比,工做中咱們處理平常事務;python
能夠將上述示例中,作事的主體:人,理解成計算機的CPU,而第二種作事方式,能夠簡單的理解成多任務!linux
這裏須要瞭解一下,計算機是由硬件和軟件組成的。git
硬件中:CPU是計算機的核心,它承擔計算機的全部任務。算法
軟件中:操做系統是運行在硬件之上的軟件,是計算機的管理者,它負責資源的管理和分配、任務的調度。編程
程序是運行在系統上的具備某種功能的軟件,好比說瀏覽器,音樂播放器等。 每當執行程序的時候,都會完成必定的功能,好比說瀏覽器幫咱們打開網頁等!爲了保證每一個程序的獨立性,就須要一個專門的管理和控制執行程序的數據結構——進程控制塊。瀏覽器
Come on!一大波概念來襲!!!緩存
什麼是進程(Process)?服務器
狹義定義:進程就是一段程序的執行過程。網絡
廣義定義:進程是具備必定獨立功能的程序,關於某個數據集合上的一次運行活動,是系統進行資源分配和調度的一個獨立單位。
進程的概念主要有兩點:
- 第一,進程是一個實體。每個進程都有它本身的地址空間,通常狀況下,包括文本區域(text region)、數據區域(data region)和堆棧(stack region)。文本區域存儲處理器執行的代碼;數據區域存儲變量和進程執行期間使用的動態分配的內存;堆棧區域存儲着活動過程當中調用的指令和本地變量。
- 第二,進程是一個「執行中的程序」。程序是一個沒有生命的實體,只有處理器賦予程序生命時,它才能成爲一個活動的實體,咱們稱其爲進程。
進程的狀態: 進程有三個狀態,就緒,運行和阻塞。
- 就緒狀態其實就是獲取了除cpu外的全部資源,只要處理器分配資源立刻就能夠運行。
- 運行態就是獲取了處理器分配的資源,程序開始執行。
- 阻塞態,當程序條件不夠時,須要等待條件知足時候才能執行,如等待I/O操做的時候,此刻的狀態就叫阻塞態。
進程的特徵:
- 動態性:進程是程序的一次執行過程,是臨時的,有生命期的,是動態產生,動態消亡的;
- 併發性:任何進程均可以同其餘進程一塊兒併發執行;
- 獨立性:進程是系統進行資源分配和調度的一個獨立單位;
- 結構性:進程由程序、數據和進程控制塊三部分組成。
什麼是線程(thread)?
定義:線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。
線程起源:
- 在早期的操做系統中並無線程的概念,進程是能擁有資源和獨立運行的最小單位,也是程序執行的最小單位。任務調度採用的是時間片輪轉的搶佔式調度方式,而進程是任務調度的最小單位,每一個進程有各自獨立的一塊內存,使得各個進程之間內存地址相互隔離。
- 後來,隨着計算機的發展,對CPU的要求愈來愈高,進程之間的切換開銷較大,已經沒法知足愈來愈複雜的程序的要求了,因而就發明了線程。
線程特徵:
- 線程本身基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),一個線程能夠建立和撤銷另外一個線程;
- 一般在一個進程中能夠包含若干個線程,固然一個進程中至少有一個線程,否則沒有存在的意義。線程能夠利用進程所擁有的資源。在引入線程的操做系統中,一般都是把進程做爲分配資源的基本單位,而把線程做爲獨立運行和獨立調度的基本單位,因爲線程比進程更小,基本上不擁有系統資源,故對它的調度所付出的開銷就會小得多,能更高效的提升系統多個程序間併發執行的程度。
進程 和 線程的區別
一、一個程序至少有一個進程,一個進程至少有一個線程;
進程是資源分配的最小單位,線程是CPU調度的最小單位。
二、資源(內存、寄存器等)分配給進程,進程在執行過程擁有獨立的內存空間,而同一進程下的全部線程共享全部資源,從而提升程序的運行效率;
三、處理機分配給線程,即處理機真正運行的是線程;
四、線程在執行過程當中,須要協做同步。不一樣線程間的要利用通訊協議來實現同步。
協程,使用一個線程去完成多個任務,能夠理解成微線程。
協程的調度徹底由用戶控制,相對獨立,有本身的上下文。一個線程能夠有多個協程,用戶建立了幾個線程,而後每一個線程都是循環按照指定的任務清單順序完成不一樣的任務,當任務被阻塞的時候執行下一個任務,當恢復的時候再回來執行這個任務,任務之間的切換隻須要保存每一個任務的上下文內容,就像直接操做棧同樣的,這樣就徹底沒有內核切換的開銷,能夠不加鎖的訪問全局變量,因此上下文的切換很是快;另外協程還須要保證是非阻塞的且沒有相互依賴,協程基本上不能同步通信,多采用異步的消息通信,效率比較高。
協程的適用場景:當程序中存在大量不須要CPU的操做時(IO),適用於協程;
多進程和多線程 是兩個不一樣的概念!!!
什麼是多進程?
進程是操做系統進行資源分配的最小單位。
在同一個時間裏,同一個計算機系統中若是容許兩個或兩個以上的進程處於運行狀態,這即是多任務。現代的操做系統幾乎都是多任務操做系統,可以同時管理多個進程的運行。 多任務帶來的好處是明顯的,好比你能夠邊聽mp3邊上網,與此同時甚至能夠將下載的文檔打印出來,而這些任務之間絲絕不會相互干擾。
那麼這裏就涉及到並行的問題,俗話說,一心不能二用,這對計算機也同樣,原則上一個CPU只能分配給一個進程,以便運行這個進程。咱們一般使用的計算機中只有一個CPU,也就是說只有一顆心,要讓它一心多用,同時運行多個進程,就必須使用併發技術。實現併發技術至關複雜,最容易理解的是「時間片輪轉進程調度算法」,它的思想簡單介紹以下:在操做系統的管理下,全部正在運行的進程輪流使用CPU,每一個進程容許佔用CPU的時間很是短(好比10毫秒),這樣用戶根本感受不出來 CPU是在輪流爲多個進程服務,就好象全部的進程都在不間斷地運行同樣。但實際上在任何一個時間內有且僅有一個進程佔有CPU。
假如一臺計算機有多個CPU,若是進程數小於CPU數,則不一樣的進程能夠分配給不一樣的CPU來運行,這樣,多個進程就是真正同時運行的,這即是並行;但若是進程數大於CPU數,則仍然須要使用併發技術。
什麼是多線程?
在計算機編程中,一個基本的概念就是同時對多個任務加以控制。許多程序設計問題都要求程序可以停下手頭的工做,改成處理其餘一些問題,再返回主進程。
最開始,線程只是用於分配單個處理器的處理時間的一種工具。但假如操做系統自己支持多個處理器,那麼每一個線程均可分配給一個不一樣的處理器,真正進入「並行運算」狀態。
線程處理很是簡單,但必須注意一個問題:共享資源!若是有多個線程同時運行,並且它們試圖訪問相同的資源,就可能會遇到問題。對那些可共享的資源來講(好比打印機),它們在使用期間必須進入鎖定狀態。因此一個線程可將資源鎖定,在完成了它的任務後,再解開(釋放)這個鎖,使其餘線程能夠接着使用一樣的資源。
一個採用了多線程技術的應用程序能夠更好地利用系統資源。其主要優點在於充分利用了CPU的空閒時間片,能夠用盡量少的時間來對用戶的要求作出響應,使得進程的總體運行效率獲得較大提升,同時加強了應用程序的靈活性。更爲重要的是,因爲同一進程的全部線程是共享同一內存,因此不須要特殊的數據傳送機制,不須要創建共享存儲區或共享文件,從而使得不一樣任務之間的協調操做與運行、數據的交互、資源的分配等問題更加易於解決。
多進程 多線程 的優缺點
多進程和多線程,是實現多任務最經常使用的兩種方式。如今,咱們來討論一下這兩種方式的優缺點。
要實現多任務,一般咱們會設計Master-Worker模式,Master負責分配任務,Worker負責執行任務,所以,多任務環境下,一般是一個Master,多個Worker。
- 若是用多進程實現Master-Worker,主進程就是Master,其餘進程就是Worker。
- 若是用多線程實現Master-Worker,主線程就是Master,其餘線程就是Worker。
多進程 優勢 是穩定性高,由於一個子進程崩潰了,不會影響主進程和其餘子進程。(固然主進程掛了全部進程就全掛了,可是Master進程只負責分配任務,掛掉的機率低)著名的Apache最先就是採用多進程模式。
多進程 缺點 是建立進程的代價大,在Unix/Linux系統下,用
fork
調用還行,在Windows下建立進程開銷巨大。另外,操做系統能同時運行的進程數也是有限的,在內存和CPU的限制下,若是有幾千個進程同時運行,操做系統連調度都會成問題。還有一個不足之處,多進程下程序與各進程之間的通訊和數據共享不方便;多線程 優勢 一般比多進程快一點,可是也快不到哪去,開銷小!
多線程主要是爲了節約CPU時間,爲了同步完成多項任務,不是爲了提升運行效率,而是爲了提升資源使用效率來提升系統的效率!
多線程 缺點 任何一個線程掛掉均可能直接形成整個進程崩潰,由於全部線程共享進程的內存。在Windows上,若是一個線程執行的代碼出了問題,你常常能夠看到這樣的提示:「該程序執行了非法操做,即將關閉」,其實每每是某個線程出了問題,可是操做系統會強制結束整個進程。
額外補充
實際開發中 何時用多線程? 何時用多進程?
在TCP/IP協議族中,使用頻率最高的上層協議無非是TCP,UDP,HTTP這3者了。
其中HTTP用在B/S架構通訊中,前兩種協議用在C/S架構中。
TCP是面向鏈接的,UDP面向非鏈接。同時,多進程間是資源隔離的,多線程間是資源共享的。
基於以上緣由,從程序設計難易程度上說:
採用TCP協議的軟件用多進程更簡單,採用UDP協議的軟件兩者均可。
這並非惟一的,只是基於各人的經驗,以及技術特性致使的複雜程度不一樣做爲參考標準。
實質上,無論採用什麼協議,採用任何多任務技術均可以在理論上實現。然而,軟件追求的是高效和穩定,因此在實際軟件設計中,便會參考軟件的實現難度和維護性等指標。
下面根據經驗簡單闡述協議與多任務方式的組合特色:
1. TCP+多進程,因爲TCP是面向鏈接的,而一個服務器的單進程空間內若是多個子任務都能看到鏈接的話,保存鏈接的資源會增多,同時各資源維護難度也隨之加大。故而採用多進程的話(多線程間資源是共享的),子進程各自獨享資源,每一個鏈接對應一個子進程,這樣進程之間的資源是天然隔離的,這就省去了管理的代價。
2. UDP+多線程,在UDP程序中,服務端採用多進程仍是多線程均可。由於UDP面向非鏈接,就省去了管理每一個鏈接的成本,這樣的話採用多線程便可。但實際狀況須要根據特性而定。
假如一臺計算機有多個CPU,若是進程數小於CPU數,則不一樣的進程能夠分配給不一樣的CPU來運行,這樣,多個進程就是真正同時運行的,這即是並行。但若是進程數大於CPU數,則仍然須要使用併發技術。
在Windows中,進行CPU分配是以線程爲單位的,一個進程可能由多個線程組成,這時狀況更加複雜,但簡單地說,有以下關係:
總線程數<= CPU數量:並行運行
總線程數> CPU數量:併發運行
多任務操做系統(如Windows)的基本原理是:操做系統將CPU的時間片分配給多個線程,每一個線程在操做系統指定的時間片內完成(注意,這裏的多個線程是分屬於不一樣進程的)。操做系統不斷的從一個線程的執行切換到另外一個線程的執行,如此往復,宏觀上看來,就好像是多個線程在一塊兒執行。因爲這多個線程分屬於不一樣的進程,所以在咱們看來,就好像是多個進程在同時執行,這樣就實現了多任務。
雖然有的時候咱們用多任務併發,充分發揮CPU等資源,來達到快速處理的任務的目的,可是是否是併發量越大越快呢,答案顯然是不對的。
咱們打個比方,假設你正在準備考試,天天晚上須要作語文、數學、英語、物理、化學這5科的做業,每項做業耗時1小時。
若是你先花1小時作語文做業,作完了,再花1小時作數學做業,這樣,依次所有作完,一共花5小時,這種方式稱爲單任務模型,或者批處理任務模型。
假設你打算切換到多任務模型,能夠先作1分鐘語文,再切換到數學做業,作1分鐘,再切換到英語,以此類推,只要切換速度足夠快,這種方式就和單核CPU執行多任務是同樣的,以幼兒園小朋友的眼光來看,你就正在同時寫5科做業。
可是,切換做業是有代價的,好比從語文切到數學,要先收拾桌子上的語文書本、鋼筆(這叫保存現場),而後,打開數學課本、找出圓規直尺(這叫準備新環境),才能開始作數學做業。操做系統在切換進程或者線程時也是同樣的,它須要先保存當前執行的現場環境(CPU寄存器狀態、內存頁等),而後,把新任務的執行環境準備好(恢復上次的寄存器狀態,切換內存頁等),才能開始執行。這個切換過程雖然很快,可是也須要耗費時間。若是有幾千個任務同時進行,操做系統可能就主要忙着切換任務,根本沒有多少時間去執行任務了,這種狀況最多見的就是硬盤狂響,點窗口無反應,系統處於假死狀態。
因此,多任務一旦多到一個限度,就會消耗掉系統全部的資源,結果效率急劇降低,全部任務都作很差。
IO在計算機中指Input/Output,也就是輸入和輸出。因爲程序和運行時數據是在內存中駐留,由CPU這個超快的計算核心來執行,涉及到數據交換的地方,一般是磁盤、網絡等,就須要IO接口。
好比你打開瀏覽器,訪問新浪首頁,瀏覽器這個程序就須要經過網絡IO獲取新浪的網頁。瀏覽器首先會發送數據給新浪服務器,告訴它我想要首頁的HTML,這個動做是往外發數據,叫Output,隨後新浪服務器把網頁發過來,這個動做是從外面接收數據,叫Input。
因此,一般,程序完成IO操做會有Input和Output兩個數據流。固然也有隻用一個的狀況,好比,從磁盤讀取文件到內存,就只有Input操做,反過來,把數據寫到磁盤文件裏,就只是一個Output操做。
IO編程中,Stream(流)是一個很重要的概念,能夠把流想象成一個水管,數據就是水管裏的水,可是隻能單向流動。Input Stream就是數據從外面(磁盤、網絡)流進內存,Output Stream就是數據從內存流到外面去。對於瀏覽網頁來講,瀏覽器和新浪服務器之間至少須要創建兩根水管,才能夠既能發數據,又能收數據。
因爲CPU和內存的速度遠遠高於外設的速度,因此,在IO編程中,就存在速度嚴重不匹配的問題。舉個例子來講,好比要把100M的數據寫入磁盤,CPU輸出100M的數據只須要0.01秒,但是磁盤要接收這100M數據可能須要10秒,怎麼辦呢?有兩種辦法:
第一種是CPU等着,也就是程序暫停執行後續代碼,等100M的數據在10秒後寫入磁盤,再接着往下執行,這種模式稱爲同步IO;
另外一種方法是CPU不等待,只是告訴磁盤,「您老慢慢寫,不着急,我接着幹別的事去了」,因而,後續代碼能夠馬上接着執行,這種模式稱爲異步IO。
很明顯,使用異步IO來編寫程序性能會遠遠高於同步IO,可是異步IO的缺點是編程模型複雜,異步IO的複雜度遠遠高於同步IO。
操做IO的能力都是由操做系統提供的,每一種編程語言都會把操做系統提供的低級C接口封裝起來方便使用,Python也不例外。
是否採用多任務的第二個考慮是任務的類型。咱們能夠把任務分爲計算密集型和IO密集型。
計算密集型任務的特色是要進行大量的計算,消耗CPU資源,好比計算圓周率、對視頻進行高清解碼等等,全靠CPU的運算能力。這種計算密集型任務雖然也能夠用多任務完成,可是任務越多,花在任務切換的時間就越多,CPU執行任務的效率就越低,因此,要最高效地利用CPU,計算密集型任務同時進行的數量應當等於CPU的核心數。
計算密集型任務因爲主要消耗CPU資源,所以,代碼運行效率相當重要。Python這樣的腳本語言運行效率很低,徹底不適合計算密集型任務。對於計算密集型任務,最好用C語言編寫。
第二種任務的類型是IO密集型,涉及到網絡、磁盤IO的任務都是IO密集型任務,這類任務的特色是CPU消耗不多,任務的大部分時間都在等待IO操做完成(由於IO的速度遠遠低於CPU和內存的速度)。對於IO密集型任務,任務越多,CPU效率越高,但也有一個限度。常見的大部分任務都是IO密集型任務,好比Web應用。IO密集型任務執行期間,99%的時間都花在IO上,花在CPU上的時間不多,所以,用運行速度極快的C語言替換用Python這樣運行速度極低的腳本語言,徹底沒法提高運行效率。對於IO密集型任務,最合適的語言就是開發效率最高(代碼量最少)的語言,腳本語言是首選,C語言最差。
同步:
同步:就是在發出一個功能調用時,在沒有獲得結果以前,該調用就不返回。
按照這個定義,其實絕大多數函數都是同步調用(例如sin isdigit等)。
可是通常而言,咱們在說同步、異步的時候,特指那些須要其餘部件協做或者須要必定時間完成的任務。
最多見的例子就是 SendMessage。
該函數發送一個消息給某個窗口,在對方處理完消息以前,這個函數不返回。
當對方處理完畢之後,該函數才把消息處理函數所返回的值返回給調用者。
異步的概念和同步相對
當一個異步過程調用發出後,調用者不會馬上獲得結果。
實際處理這個調用的部件是在調用發出後,
經過狀態、通知來通知調用者,或經過回調函數處理這個調用。考慮到CPU和IO之間巨大的速度差別,一個任務在執行的過程當中大部分時間都在等待IO操做,單進程單線程模型會致使別的任務沒法並行執行,所以,咱們才須要多進程模型或者多線程模型來支持多任務併發執行。
現代操做系統對IO操做已經作了巨大的改進,最大的特色就是支持異步IO。若是充分利用操做系統提供的異步IO支持,就能夠用單進程單線程模型來執行多任務,這種全新的模型稱爲事件驅動模型,Nginx就是支持異步IO的Web服務器,它在單核CPU上採用單進程模型就能夠高效地支持多任務。在多核CPU上,能夠運行多個進程(數量與CPU核心數相同),充分利用多核CPU。因爲系統總的進程數量十分有限,所以操做系統調度很是高效。用異步IO編程模型來實現多任務是一個主要的趨勢。
對應到Python語言,單進程的異步編程模型稱爲協程,有了協程的支持,就能夠基於事件驅動編寫高效的多任務程序。
阻塞/非阻塞, 它們是程序在等待消息(無所謂同步或者異步)時的狀態。
阻塞
阻塞調用是指調用結果返回以前,當前線程會被掛起。函數只有在獲得結果以後纔會返回。
有人也許會把阻塞調用和同步調用等同起來,實際上他是不一樣的。
對於同步調用來講,不少時候當前線程仍是激活的,只是從邏輯上當前函數沒有返回而已。socket接收數據函數recv是一個阻塞調用的例子。
當socket工做在阻塞模式的時候, 若是沒有數據的狀況下調用該函數,則當前線程就會被掛起,直到有數據爲止。
非阻塞
非阻塞和阻塞的概念相對應,指在不能馬上獲得結果以前,該函數不會阻塞當前線程,而會馬上返回。
不少人也會把異步和非阻塞混淆,
由於異步操做通常都不會在真正的IO 操做處被阻塞,
好比若是用select 函數,當select 返回可讀時再去read 通常都不會被阻塞
就比如當你的號碼排到時通常都是在你以前已經沒有人了,因此你再去櫃檯辦理業務就不會被阻塞.
可見,同步/異步與阻塞/非阻塞是兩組不一樣的概念,它們能夠共存組合,
而不少人之因此把同步和阻塞混淆,我想也是由於沒有區分這兩個概念,
好比阻塞的read/write 操做中,實際上是把消息通知和處理消息結合在了一塊兒,
在這裏所關注的消息就是fd 是否可讀/寫,而處理消息則是對fd 讀/寫.
當咱們將這個fd 設置爲非阻塞的時候,read/write 操做就不會在等待消息通知這裏阻塞,
若是fd 不可讀/寫則操做當即返回.
同步/異步與阻塞/非阻塞的組合分析
_____|_____阻塞_______|_____非阻塞_____
同步 | 同步阻塞 | 同步非阻塞
異步 | 異步阻塞 | 異步非阻塞
同步阻塞形式:
效率是最低的,
拿上面的例子來講,就是你專心排隊,什麼別的事都不作。
實際程序中
就是未對fd 設置O_NONBLOCK 標誌位的read/write 操做,
異步阻塞形式:
若是在銀行等待辦理業務的人採用的是異步的方式去等待消息被觸發,也就是領了一張小紙條,
假如在這段時間裏他不能離開銀行作其它的事情,那麼很顯然,這我的被阻塞在了這個等待的操做上面;
異步操做是能夠被阻塞住的,只不過它不是在處理消息時阻塞,而是在等待消息被觸發時被阻塞.
好比select 函數,
假如傳入的最後一個timeout 參數爲NULL,那麼若是所關注的事件沒有一個被觸發,
程序就會一直阻塞在這個select 調用處.
同步非阻塞形式:
其實是效率低下的,
想象一下你一邊打着電話一邊還須要擡頭看到底隊伍排到你了沒有,
若是把打電話和觀察排隊的位置當作是程序的兩個操做的話,
這個程序須要在這兩種不一樣的行爲之間來回的切換,效率可想而知是低下的;
不少人會寫阻塞的read/write 操做,
可是別忘了能夠對fd 設置O_NONBLOCK 標誌位,這樣就能夠將同步操做變成非阻塞的了;
異步非阻塞形式:
效率更高,
由於打電話是你(等待者)的事情,而通知你則是櫃檯(消息觸發機制)的事情,
程序沒有在兩種不一樣的操做中來回切換.
好比說,這我的忽然發覺本身煙癮犯了,須要出去抽根菸,
因而他告訴大堂經理說,排到我這個號碼的時候麻煩到外面通知我一下(註冊一個回調函數),
那麼他就沒有被阻塞在這個等待的操做上面,天然這個就是異步+非阻塞的方式了.
若是使用異步非阻塞的狀況,
好比aio_*組的操做,當發起一個aio_read 操做時,函數會立刻返回不會被阻塞,
當所關注的事件被觸發時會調用以前註冊的回調函數進行處理,
Python多線程、多進程實現:http://www.cnblogs.com/hellojesson/p/6397173.html
Python操做異步IO\隊列\緩存:http://www.cnblogs.com/alex3714/articles/5248247.html
IO多路複用的幾種實現機制的分析: http://blog.csdn.net/zhang_shuai_2011/article/details/7675797
知乎關於I/O多路複用的解答:https://www.zhihu.com/question/32163005