併發

進程:html

一.爲何要有操做系統:

現代的計算機系統主要是由一個或者多個處理器,主存,硬盤,鍵盤,鼠標,顯示器,打印機,網絡接口及其餘輸入輸出設備組成。
  通常而言,現代計算機系統是一個複雜的系統。
  其一:若是每位應用程序員都必須掌握該系統全部的細節,那就不可能再編寫代碼了(嚴重影響了程序員的開發效率:所有掌握這些細節可能須要一萬年....)
  其二:而且管理這些部件並加以優化使用,是一件極富挑戰性的工做,因而,計算安裝了一層軟件(系統軟件),稱爲操做系統。它的任務就是爲用戶程序提供一個更好、更簡單、更清晰的計算機模型,並管理剛纔提到的全部設備。

 

總結:
程序員沒法把全部的硬件操做細節都瞭解到,管理這些硬件而且加以優化使用是很是繁瑣的工做,這個繁瑣的工做就是操做系統來乾的,有了他,程序員就從這些繁瑣的工做中解脫了出來,只須要考慮本身的應用軟件的編寫就能夠了,應用軟件直接使用操做系統提供的功能來間接使用硬件。

二. 什麼是操做系統:

 精簡的說的話,操做系統就是一個協調、管理和控制計算機硬件資源和軟件資源的控制程序。操做系統所處的位置如圖1
細說的話,操做系統應該分紅兩部分功能:
#一:隱藏了醜陋的硬件調用接口(鍵盤、鼠標、音箱等等怎麼實現的,就不須要你管了),爲應用程序員提供調用硬件資源的更好,更簡單,更清晰的模型(系統調用接口)。應用程序員有了這些接口後,就不用再考慮操做硬件的細節,專心開發本身的應用程序便可。例如:操做系統提供了文件這個抽象概念,對文件的操做就是對磁盤的操做,有了文件咱們無需再去考慮關於磁盤的讀寫控制(好比控制磁盤轉動,移動磁頭讀寫數據等細節),
 
#二:將應用程序對硬件資源的競態請求變得有序化
例如:不少應用軟件實際上是共享一套計算機硬件,比方說有可能有三個應用程序同時須要申請打印機來輸出內容,那麼a程序競爭到了打印機資源就打印,而後多是b競爭到打印機資源,也多是c,這就致使了無序,打印機可能打印一段a的內容而後又去打印c...,操做系統的一個功能就是將這種無序變得有序。
 
 
 
#做用一:爲應用程序提供如何使用硬件資源的抽象例如:操做系統提供了文件這個抽象概念,對文件的操做就是對磁盤的操做,有了文件咱們無需再去考慮關於磁盤的讀寫控制
 
注意:
操做系統提供給應用程序的該抽象是簡單,清晰,優雅的。爲什麼要提供該抽象呢?
硬件廠商須要爲操做系統提供本身硬件的驅動程序(設備驅動,這也是爲什麼咱們要使用聲卡,就必須安裝聲卡驅動。。。),廠商爲了節省成本或者兼容舊的硬件,它們的驅動程序是複雜且醜陋的
操做系統就是爲了隱藏這些醜陋的信息,從而爲用戶提供更好的接口
這樣用戶使用的shell,Gnome,KDE看到的是不一樣的界面,但其實都使用了同一套由linux系統提供的抽象接口
 
 
#做用二:管理硬件資源
現代的操做系統運行同時運行多道程序,操做系統的任務是在相互競爭的程序之間有序地控制對處理器、存儲器以及其餘I/O接口設備的分配。
例如:
同一臺計算機上同時運行三個程序,它們三個想在同一時刻在同一臺計算機上輸出結果,那麼開始的幾行多是程序1的輸出,接着幾行是程序2的輸出,而後又是程序3的輸出,最終將是一團糟(程序之間是一種互相競爭資源的過程)
操做系統將打印機的結果送到磁盤的緩衝區,在一個程序徹底結束後,纔將暫存在磁盤上的文件送到打印機輸出,同時其餘的程序能夠繼續產生更多的輸出結果(這些程序的輸出沒有真正的送到打印機),這樣,操做系統就將由競爭產生的無序變得有序化。

三.操做系統與普通軟件的區別

1.主要區別是:你不想用暴風影音了你能夠選擇用迅雷播放器或者乾脆本身寫一個,可是你沒法寫一個屬於操做系統一部分的程序(時鐘中斷處理程序),操做系統由硬件保護,不能被用戶修改。
  2.操做系統與用戶程序的差別並不在於兩者所處的地位。特別地,操做系統是一個大型、複雜、長壽的軟件,
  • 大型:linux或windows的源代碼有五百萬行數量級。按照每頁50行共1000行的書來算,五百萬行要有100卷,要用一整個書架子來擺置,這還僅僅是內核部分。用戶程序,如GUI,庫以及基本應用軟件(如windows Explorer等),很容易就能達到這個數量的10倍或者20倍之多。
  • 長壽:操做系統很難編寫,如此大的代碼量,一旦完成,操做系統全部者便不會輕易扔掉,再寫一個。而是在原有的基礎上進行改進。(基本上能夠把windows95/98/Me看出一個操做系統,而windows NT/2000/XP/Vista則是兩位一個操做系統,對於用戶來講它們十分類似。還有UNIX以及它的變體和克隆版本也演化了多年,如System V版,Solaris以及FreeBSD等都是Unix的原始版,不過儘管linux很是依照UNIX模式而仿製,而且與UNIX高度兼容,可是linux具備全新的代碼基礎)

四 操做系統發展史

第一代(1940~1955)  手工操做---穿孔卡片

第二代(1955~1965)  磁帶存儲---批處理系統

   1.聯機批處理系統

首先出現的是聯機批處理系統,即做業的輸入/輸出由CPU來處理。

2.脫機批處理系統

    爲克服與緩解:高速主機與慢速外設(輸入輸出設備)的矛盾,提升CPU的利用率,又引入了 脫機批處理系統,即輸入/輸出(input\output,簡稱I\O操做)脫離主機控制。
衛星機:一臺不與主機直接相連而專門用於與輸入/輸出設備打交道的。

第三代(1955~1965)  多道程序系統(多道是重點*****)

 多道技術產生的技術背景:cpu在執行一個任務的過程當中,若須要操做硬盤,則發送操做硬盤的指令,指令一旦發出,硬盤上的機械手臂滑動讀取數據到內存中,這一段時間,cpu須要等待,時間可能很短,但對於cpu來講已經很長很長,長到可讓cpu作不少其餘的任務,若是咱們讓cpu在這段時間內切換到去作其餘的任務,這樣cpu不就充分利用了嗎。
  1.多道程序設計技術
   所謂多道程序設計技術,就是指容許多個程序同時進入內存並運行。即同時把多個程序放入內存,並容許它們交替在CPU中運行,它們共享系統中的各類硬、軟件資源。當一道程序因I/O請求而暫停運行時,CPU便當即轉去運行另外一道程序。
   在A程序計算時,I/O空閒, A程序I/O操做時,CPU空閒(B程序也是一樣);必須A工做完成後,B才能進入內存中開始工做,二者是串行的,所有完成共需時間=T1+T2。
將A、B兩道程序同時存放在內存中,它們在系統的控制下,可相互穿插、交替地在CPU上運行:當A程序因請求I/O操做而放棄CPU時,B程序就可佔用CPU運行,這樣 CPU再也不空閒,而正進行A I/O操做的I/O設備也不空閒,顯然,CPU和I/O設備都處於「忙」狀態,大大提升了資源的利用率,從而也提升了系統的效率,A、B所有完成所需時間<<T1+T2。
    多道程序設計技術不只使CPU獲得充分利用,同時改善I/O設備和內存的利用率,從而提升了整個系統的資源利用率和系統吞吐量(單位時間內處理做業(程序)的個數),最終提升了整個系統的效率。
    單處理機系統中多道程序運行時的特色:
      (1)多道:計算機內存中同時存放幾道相互獨立的程序;
      (2)宏觀上並行:同時進入系統的幾道程序都處於運行過程當中,即它們前後開始了各自的運行,但都未運行完畢;
      (3)微觀上串行:實際上,各道程序輪流地用CPU,並交替運行。
    多道程序系統的出現,標誌着操做系統漸趨成熟的階段,前後出現了做業調度管理、處理機管理、存儲器管理、外部設備管理、文件系統管理等功能。
    因爲多個程序同時在計算機中運行,開始有了空間隔離的概念,只有內存空間的隔離,才能讓數據更加安全、穩定。
    出了空間隔離以外,多道技術還第一次體現了時空複用的特色,遇到IO操做就切換程序,使得cpu的利用率提升了,計算機的工做效率也隨之提升。
    空間上的複用:將內存分爲幾部分,每一個部分放入一個程序,這樣,同一時間內存中就有了多道程序。
    時間上的複用:當一個程序在等待I/O時,另外一個程序能夠使用cpu,若是內存中能夠同時存放足夠多的做業,則cpu的利用率能夠接近100%
爲了解決空間複用上的問題:程序之間的內存必須分割,由操做系統控制。若是內存彼此不分割,則一個程序能夠訪問另一個程序的內存。
 爲了解決時間出現了分時系統  
3.分時系統
    因爲CPU速度不斷提升和採用分時技術,一臺計算機可同時鏈接多個用戶終端,而每一個用戶可在本身的終端上聯機使用計算機,好象本身獨佔機器同樣。
 
 分時技術:把處理機的運行時間分紅很短的時間片,按時間片輪流把處理機分配給各聯機做業使用。
    若某個做業在分配給它的時間片內不能完成其計算,則該做業暫時中斷,把處理機讓給另外一做業使用,等待下一輪時再繼續其運行。因爲計算機速度很快,做業運行輪轉得很快,給每一個用戶的印象是,好象他獨佔了一臺計算機。而每一個用戶能夠經過本身的終端向系統發出各類操做控制命令,在充分的人機交互狀況下,完成做業的運行。
具備上述特徵的計算機系統稱爲分時系統,它容許多個用戶同時聯機使用計算機。
    特色:
    (1)多路性。若干個用戶同時使用一臺計算機。微觀上看是各用戶輪流使用計算機;宏觀上看是各用戶並行工做。
    (2)交互性。用戶可根據系統對請求的響應結果,進一步向系統提出新的請求。這種能使用戶與系統進行人機對話的工做方式,明顯地有別於批處理系統,於是,分時系統又被稱爲交互式系統。
    (3)獨立性。用戶之間能夠相互獨立操做,互不干擾。系統保證各用戶程序運行的完整性,不會發生相互混淆或破壞現象。
    (4)及時性。系統可對用戶的輸入及時做出響應。分時系統性能的主要指標之一是響應時間,它是指:從終端發出命令到系統予以應答所需的時間。
    分時系統的主要目標:對用戶響應的及時性,即不至於用戶等待每個命令的處理時間過長。
分時系統能夠同時接納數十個甚至上百個用戶,因爲內存空間有限,每每採用對換(又稱交換)方式的存儲方法。即將未「輪到」的做業放入磁盤,一旦「輪到」,再將其調入內存;而時間片用完後,又將做業存回磁盤(俗稱「滾進」、「滾出「法),使同一存儲區域輪流爲多個用戶服務。
多用戶分時系統是當今計算機操做系統中最廣泛使用的一類操做系統。
        注意:分時系統的分時間片工做,在沒有遇到IO操做的時候就用完了本身的時間片被切走了,這樣的切換工做其實並無提升cpu的效率,反而使得計算機的效率下降了。爲何降低了呢?由於CPU須要切換,而且記錄每次切換程序執行到了哪裏,以便下次再切換回來的時候可以繼續以前的程序,雖然咱們犧牲了一點效率,可是卻實現了多個程序共同執行的效果,這樣你就能夠在計算機上一邊聽音樂一邊聊qq了。
 4.實時系統
實時系統可分紅兩類:
      (1)實時控制系統。當用于飛機飛行、導彈發射等的自動控制時,要求計算機能儘快處理測量系統測得的數據,及時地對飛機或導彈進行控制,或將有關信息經過顯示終端提供給決策人員。當用於軋鋼、石化等工業生產過程控制時,也要求計算機能及時處理由各種傳感器送來的數據,而後控制相應的執行機構。
      (2)實時信息處理系統。當用於預約飛機票、查詢有關航班、航線、票價等事宜時,或當用於銀行系統、情報檢索系統時,都要求計算機能對終端設備發來的服務請求及時予以正確的回答。此類對響應及時性的要求稍弱於第一類。
實時操做系統的主要特色:
(1)及時響應。每個信息接收、分析處理和發送的過程必須在嚴格的時間限制內完成。
(2)高可靠性。需採起冗餘措施,雙機系統先後臺工做,也包括必要的保密措施等。
分時——如今流行的PC,服務器都是採用這種運行模式,即把CPU的運行分紅若干時間片分別處理不一樣的運算請求 linux系統 實時——通常用於單片機上、PLC等,好比電梯的上下控制中,對於按鍵等動做要求進行實時處理
   5.通用操做系統
操做系統的三種基本類型:多道批處理系統、分時系統、實時系統。
    通用操做系統:具備多種類型操做特徵的操做系統。能夠同時兼有多道批處理、分時、實時處理的功能,或其中兩種以上的功能。
 
多道
 
 
多道技術背景:
  提升工做效率(充分利用IO阻塞的時間)
  同時執行多個任務
多道技術
  空間複用:充分的利用內存空間
    時間複用:充分利用IO阻塞時間
分時系統
 
進程和程序的區別
程序:就是單純的一個文件,一堆代碼
進程即正在執行的一個過程。進程是對正在運行程序的一個抽象。
進程:正在運行的程序
主進程:當前運行的文件
 
什麼是進程
    進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。咱們本身在python文件中寫了一些代碼,這叫作程序,運行這個python文件的時候,這叫作進程。
    狹義定義:進程是正在運行的程序的實例(an instance of a computer program that is being executed)。
    廣義定義:進程是一個具備必定獨立功能的程序關於某個數據集合的一次運行活動。它是操做系統動態執行的基本單元,在傳統的操做系統中,進程既是基本的分配單元,也是基本的執行單元。
    舉例: 好比py1文件中有個變量a=1,py2文件中有個變量a=2,他們兩個會衝突嗎?不會的,是否是,由於兩個文件運行起來後是兩個進程,操做系統讓他們在內存上隔離開,對吧。
 
 
進程的概念:
    第一,進程是一個實體。每個進程都有它本身的地址空間,通常狀況下,包括文本區域(text region)(python的文件)、數據區域(data region)(python文件中定義的一些變量數據)和堆棧(stack region)。文本區域存儲處理器執行的代碼;數據區域存儲變量和進程執行期間使用的動態分配的內存;堆棧區域存儲着活動過程調用的指令和本地變量。
第二,進程是一個「執行中的程序」。程序是一個沒有生命的實體,只有處理器賦予程序生命時(操做系統執行之),它才能成爲一個活動的實體,咱們稱其爲進程。[3]
進程是操做系統中最基本、重要的概念。是多道程序系統出現後,爲了刻畫系統內部出現的動態狀況,描述系統內部各道程序的活動規律引進的一個概念,全部多道程序設計操做系統都創建在進程的基礎上。
進程的特徵:
    動態性:進程的實質是程序在多道程序系統中的一次執行過程,進程是動態產生,動態消亡的。
併發性:任何進程均可以同其餘進程一塊兒併發執行
獨立性:進程是一個能獨立運行的基本單位,同時也是系統分配資源和調度的獨立單位;
異步性:因爲進程間的相互制約,使進程具備執行的間斷性,即進程按各自獨立的、不可預知的速度向前推動
結構特徵:進程由程序、數據和進程控制塊三部分組成。
多個不一樣的進程能夠包含相同的程序:一個程序在不一樣的數據集裏就構成不一樣的進程,能獲得不一樣的結果;可是執行過程當中,程序不能發生改變。
進程與程序中的區別:
    程序是指令和數據的有序集合,其自己沒有任何運行的含義,是一個靜態的概念。
而進程是程序在處理機上的一次執行過程,它是一個動態的概念。
程序能夠做爲一種軟件資料長期存在,而進程是有必定生命期的。
程序是永久的,進程是暫時的。
舉例:就像qq同樣,qq是咱們安裝在本身電腦上的客戶端程序,其實就是一堆的代碼文件,咱們不運行qq,那麼他就是一堆代碼程序,當咱們運行qq的時候,這些代碼運行起來,就成爲一個進程了。
注意:同一個程序執行兩次,就會在操做系統中出現兩個進程,因此咱們能夠同時運行一個軟件,分別作不一樣的事情也不會混亂。好比打開暴風影音,雖然都是同一個軟件,可是一個能夠播放蒼井空,一個能夠播放飯島愛。

進程調度

要想多個進程交替運行,操做系統必須對這些進程進行調度,這個調度也不是隨即進行的,而是須要遵循必定的法則,由此就有了進程的調度算法。
先來先服務調度算法:
    先來先服務(FCFS)調度算法是一種最簡單的調度算法,該算法既可用於做業調度,也可用於進程調度。FCFS算法比較有利於長做業(進程),而不利於短做業(進程)。由此可知,本算法適合於CPU繁忙型做業,而不利於I/O繁忙型的做業(進程)。
短做業優先調度算法:
    短做業(進程)優先調度算法(SJ/PF)是指對短做業或短進程優先調度的算法,該算法既可用於做業調度,也可用於進程調度。但其對長做業不利;不能保證緊迫性做業(進程)被及時處理;做業的長短只是被估算出來的。
時間片輪轉法:
    時間片輪轉(Round Robin,RR)法的基本思路是讓每一個進程在就緒隊列中的等待時間與享受服務的時間成比例。在時間片輪轉法中,須要將CPU的處理時間分紅固定大小的時間片,例如,幾十毫秒至幾百毫秒。若是一個進程在被調度選中以後用完了系統規定的時間片,但又未完成要求的任務,則它自行釋放本身所佔有的CPU而排到就緒隊列的末尾,等待下一次調度。同時,進程調度程序又去調度當前就緒隊列中的第一個進程。
顯然,輪轉法只能用來調度分配一些能夠搶佔的資源。這些能夠搶佔的資源能夠隨時被剝奪,並且能夠將它們再分配給別的進程。CPU是可搶佔資源的一種。但打印機等資源是不可搶佔的。因爲做業調度是對除了CPU以外的全部系統硬件資源的分配,其中包含有不可搶佔資源,因此做業調度不使用輪轉法。
在輪轉法中,時間片長度的選取很是重要。首先,時間片長度的選擇會直接影響到系統的開銷和響應時間。若是時間片長度太短,則調度程序搶佔處理機的次數增多。這將使進程上下文切換次數也大大增長,從而加劇系統開銷。反過來,若是時間片長度選擇過長,例如,一個時間片能保證就緒隊列中所需執行時間最長的進程能執行完畢,則輪轉法變成了先來先服務法。時間片長度的選擇是根據系統對響應時間的要求和就緒隊列中所容許最大的進程數來肯定的。
在輪轉法中,加入到就緒隊列的進程有3種狀況:
一種是分給它的時間片用完,但進程還未完成,回到就緒隊列的末尾等待下次調度去繼續執行。
另外一種狀況是分給該進程的時間片並未用完,只是由於請求I/O或因爲進程的互斥與同步關係而被阻塞。當阻塞解除以後再回到就緒隊列。
第三種狀況就是新建立進程進入就緒隊列。
若是對這些進程區別對待,給予不一樣的優先級和時間片從直觀上看,能夠進一步改善系統服務質量和效率。例如,咱們可把就緒隊列按照進程到達就緒隊列的類型和進程被阻塞時的阻塞緣由分紅不一樣的就緒隊列,每一個隊列按FCFS原則排列,各隊列之間的進程享有不一樣的優先級,但同一隊列內優先級相同。這樣,當一個進程在執行完它的時間片以後,或從睡眠中被喚醒以及被建立以後,將進入不一樣的就緒隊列。
 
多級反饋隊列:
    前面介紹的各類用做進程調度的算法都有必定的侷限性。如短進程優先的調度算法,僅照顧了短進程而忽略了長進程,並且若是並未指明進程的長度,則短進程優先和基於進程長度的搶佔式調度算法都將沒法使用。
而多級反饋隊列調度算法則沒必要事先知道各類進程所需的執行時間,並且還能夠知足各類類型進程的須要,於是它是目前被公認的一種較好的進程調度算法。在採用多級反饋隊列調度算法的系統中,調度算法的實施過程以下所述。
(1) 應設置多個就緒隊列,併爲各個隊列賦予不一樣的優先級。第一個隊列的優先級最高,第二個隊列次之,其他各隊列的優先權逐個下降。該算法賦予各個隊列中進程執行時間片的大小也各不相同,在優先權愈高的隊列中,爲每一個進程所規定的執行時間片就愈小。例如,第二個隊列的時間片要比第一個隊列的時間片長一倍,……,第i+1個隊列的時間片要比第i個隊列的時間片長一倍。
(2) 當一個新進程進入內存後,首先將它放入第一隊列的末尾,按FCFS原則排隊等待調度。當輪到該進程執行時,如它能在該時間片內完成,即可準備撤離系統;若是它在一個時間片結束時還沒有完成,調度程序便將該進程轉入第二隊列的末尾,再一樣地按FCFS原則等待調度執行;若是它在第二隊列中運行一個時間片後仍未完成,再依次將它放入第三隊列,……,如此下去,當一個長做業(進程)從第一隊列依次降到第n隊列後,在第n 隊列便採起按時間片輪轉的方式運行。
 
(3) 僅當第一隊列空閒時,調度程序才調度第二隊列中的進程運行;僅當第1~(i-1)隊列均空時,纔會調度第i隊列中的進程運行。若是處理機正在第i隊列中爲某進程服務時,又有新進程進入優先權較高的隊列(第1~(i-1)中的任何一個隊列),則此時新進程將搶佔正在運行進程的處理機,即由調度程序把正在運行的進程放回到第i隊列的末尾,把處理機分配給新到的高優先權進程。
 
 
        對於多級反饋隊列,windows不太清楚,可是在linux裏面能夠設置某個進程的優先級,提升了有限級有可能就會多執行幾個時間片。
 
併發:提升了程序的工做效率,僞並行,遇到IO就進行切換,提升了程序的運行效率兩項工做:
1:進程之間的切換
2:保護現場,保留你進程執行的狀態
並行:應用多核,多個任務在同時執行,應用多核,同時運行多個任務,真正的同時在進行進程的建立 結束等
 
 
 
經過進程之間的調度,也就是進程之間的切換,咱們用戶感知到的好像是兩個視頻文件同時在播放,或者音樂和遊戲同時在進行,那就讓咱們來看一下什麼叫作併發和並行
不管是並行仍是併發,在用戶看來都是'同時'運行的,無論是進程仍是線程,都只是一個任務而已,真是幹活的是cpu,cpu來作這些任務,而一個cpu同一時刻只能執行一個任務
  併發:是僞並行,即看起來是同時運行。單個cpu+多道技術就能夠實現併發,(並行也屬於併發)
    
    單cpu,多進程併發舉例:
        你是一個cpu,你同時談了三個女友,每個均可以是一個戀愛任務,你被這三個任務共享要玩出併發戀愛的效果, 應該是你先跟女朋友1去看電影,看了一會說:很差,我要拉肚子,而後跑去跟第二個女朋友吃飯,吃了一會說:那啥,我去趟洗手間,而後跑去跟女朋友3開了個房,而後在你的基友眼裏,你就在和三個女朋友同時在一塊兒玩。
         並行:並行:同時運行,只有具有多個cpu才能實現並行
    
    多個cpu,多個進程並行舉例:
        將多個cpu必須成高速公路上的多個車道,進程就比如每一個車道上行駛的車輛,並行就是說,你們在本身的車道上行駛,會不影響,同時在開車。這就是並行
 
 
 
  單核下,能夠利用多道技術,多個核,每一個核也均可以利用多道技術(多道技術是針對單核而言的)
  有四個核,六個任務,這樣同一時間有四個任務被執行,假設分別被分配給了cpu1,cpu2,cpu3,cpu4,
  一旦任務1遇到I/O就被迫中斷執行,此時任務5就拿到cpu1的時間片去執行,這就是單核下的多道技術
  而一旦任務1的I/O結束了,操做系統會從新調用它(需知進程的調度、分配給哪一個cpu運行,由操做系統說了算),可能被分配給四個cpu中的任意一個去執行
 
 
全部現代計算機常常會在同一時間作不少件事,一個用戶的PC(不管是單cpu仍是多cpu),均可以同時運行多個任務(一個任務能夠理解爲一個進程)。
    啓動一個進程來殺毒(360軟件)
    啓動一個進程來看電影(暴風影音)
    啓動一個進程來聊天(騰訊QQ)
  全部的這些進程都需被管理,因而一個支持多進程的多道程序系統是相當重要的
  多道技術概念回顧:內存中同時存入多道(多個)程序,cpu從一個進程快速切換到另一個,使每一個進程各自運行幾十或幾百毫秒,這樣,雖然在某一個瞬間,一個cpu只能執行一個任務,但在1秒內,cpu卻能夠運行多個進程,這就給人產生了並行的錯覺,即僞並行,以此來區分多處理器操做系統的真正硬件並行(多個cpu共享同一個物理內存)
 
 
1.進程狀態介紹
在瞭解其餘概念以前,咱們首先要了解進程的幾個狀態。在程序運行的過程當中,因爲被操做系統的調度算法控制,程序會進入幾個狀態:就緒,運行和阻塞。
  (1)就緒(Ready)狀態
    當進程已分配到除CPU之外的全部必要的資源,只要得到處理機即可當即執行,這時的進程狀態稱爲就緒狀態。
  (2)執行/運行(Running)狀態當進程已得到處理機,其程序正在處理機上執行,此時的進程狀態稱爲執行狀態。
  (3)阻塞(Blocked)狀態正在執行的進程,因爲等待某個事件發生而沒法執行時,便放棄處理機而處於阻塞狀態。引發進程阻塞的事件可有多種,例如,等待I/O完成、申請緩衝區不能知足、等待信件(信號)等。
    事件請求:input、sleep、文件輸入輸出、recv、accept等
    事件發生:sleep、input等完成了
    時間片到了以後有回到就緒狀態,這三個狀態不斷的在轉換。
 
2.同步異步
    所謂同步就是一個任務的完成須要依賴另一個任務時,只有等待被依賴的任務完成後,依賴的任務才能算完成,這是一種可靠的任務序列。要麼成功都成功,失敗都失敗,兩個任務的狀態能夠保持一致。其實就是一個程序結束才執行另一個程序,串行的,不必定兩個程序就有依賴關係。
    所謂異步是不須要等待被依賴的任務完成,只是通知被依賴的任務要完成什麼工做,依賴的任務也當即執行,只要本身完成了整個任務就算完成了。至於被依賴的任務最終是否真正完成,依賴它的任務沒法肯定,因此它是不可靠的任務序列。
    舉例:
好比咱們去樓下的老家肉餅吃飯,飯點好了,取餐的時候發生了一些同步異步的事情。
同步:咱們都站在隊裏等着取餐,前面有我的點了一份肉餅,後廚作了好久,可是因爲同步機制,咱們仍是要站在隊裏等着前面那我的的肉餅作好取走,咱們才往前走一步。
異步:咱們點完餐以後,點餐員給了咱們一個取餐號碼,跟你說,你不用在這裏排隊等着,去找個地方坐着玩手機去吧,等飯作好了,我叫你。這種機制(等待別人通知)就是異步等待消息通知。在異步消息處理中,等待消息通知者(在這個例子中等着取餐的你)每每註冊一個回調機制,在所等待的事件被觸發時由觸發機制(點餐員)經過某種機制(喊號,‘250號你的包子好了‘)找到等待該事件的人。
 
3.阻塞與非阻塞
   阻塞和非阻塞這兩個概念與程序(線程)等待消息通知(無所謂同步或者異步)時的狀態有關。也就是說阻塞與非阻塞主要是程序(線程)等待消息通知時的狀態角度來講的
    舉例:
繼續上面的那個例子,不管是排隊仍是使用號碼等待通知,若是在這個等待的過程當中,等待者除了等待消息通知以外不能作其它的事情,那麼該機制就是阻塞的,表如今程序中,也就是該程序一直阻塞在該函數調用處不能繼續往下執行。
相反,有的人喜歡在等待取餐的時候一邊打遊戲一邊等待,這樣的狀態就是非阻塞的,由於他(等待者)沒有阻塞在這個消息通知上,而是一邊作本身的事情一邊等待。阻塞的方法:input、time.sleep,socket中的recv、accept等等。
 
 4.同步/異步 與 阻塞和非阻塞
  1. 同步阻塞形式
    效率最低。拿上面的例子來講,就是你專心排隊,什麼別的事都不作。
  1. 異步阻塞形式
    若是在排隊取餐的人採用的是異步的方式去等待消息被觸發(通知),也就是領了一張小紙條,假如在這段時間裏他不能作其它的事情,就在那坐着等着,不能玩遊戲等,那麼很顯然,這我的被阻塞在了這個等待的操做上面;
    異步操做是能夠被阻塞住的,只不過它不是在處理消息時阻塞,而是在等待消息通知時被阻塞。
  1. 同步非阻塞形式
    其實是效率低下的。
    想象一下你一邊打着電話一邊還須要擡頭看到底隊伍排到你了沒有,若是把打電話和觀察排隊的位置當作是程序的兩個操做的話,這個程序須要在這兩種不一樣的行爲之間來回的切換,效率可想而知是低下的。
  1. 異步非阻塞形式
    效率更高,
    由於打電話是你(等待者)的事情,而通知你則是櫃檯(消息觸發機制)的事情,程序沒有在兩種不一樣的操做中來回切換。
    好比說,這我的忽然發覺本身煙癮犯了,須要出去抽根菸,因而他告訴點餐員說,排到我這個號碼的時候麻煩到外面通知我一下,那麼他就沒有被阻塞在這個等待的操做上面,天然這個就是異步+非阻塞的方式了。
  不少人會把同步和阻塞混淆,是由於不少時候同步操做會以阻塞的形式表現出來,一樣的,不少人也會把異步和非阻塞混淆,由於異步操做通常都不會在真正的IO操做處被阻塞。
 
拋異常
針對accept
 
 
 
 

六 進程的建立、結束與併發的實現(瞭解)

1.進程的建立
    但凡是硬件,都須要有操做系統去管理,只要有操做系統,就有進程的概念,就須要有建立進程的方式,一些操做系統只爲一個應用程序設計,好比微波爐中的控制器,一旦啓動微波爐,全部的進程都已經存在。
    而對於通用系統(跑不少應用程序),須要有系統運行過程當中建立或撤銷進程的能力,主要分爲4中形式建立新的進程
      1. 系統初始化(查看進程linux中用ps命令,windows中用任務管理器,前臺進程負責與用戶交互,後臺運行的進程與用戶無關,運行在後臺而且只在須要時才喚醒的進程,稱爲守護進程,如電子郵件、web頁面、新聞、打印)
      2. 一個進程在運行過程當中開啓了子進程(如nginx開啓多進程,os.fork,subprocess.Popen等)
      3. 用戶的交互式請求,而建立一個新進程(如用戶雙擊暴風影音)
      4. 一個批處理做業的初始化(只在大型機的批處理系統中應用)
  
    不管哪種,新進程的建立都是由一個已經存在的進程執行了一個用於建立進程的系統調用而建立的:
      1. 在UNIX中該系統調用是:fork,fork會建立一個與父進程如出一轍的副本,兩者有相同的存儲映像、一樣的環境字符串和一樣的打開文件(在shell解釋器進程中,執行一個命令就會建立一個子進程)
      2. 在windows中該系統調用是:CreateProcess,CreateProcess既處理進程的建立,也負責把正確的程序裝入新進程。
 
    關於建立的子進程,UNIX和windows
      1.相同的是:進程建立後,父進程和子進程有各自不一樣的地址空間(多道技術要求物理層面實現進程之間內存的隔離),任何一個進程的在其地址空間中的修改都不會影響到另一個進程。
      2.不一樣的是:在UNIX中,子進程的初始地址空間是父進程的一個副本,提示:子進程和父進程是能夠有隻讀的共享內存區的。可是對於windows系統來講,從一開始父進程與子進程的地址空間就是不一樣的。
 
2.進程的結束 
    1. 正常退出(自願,如用戶點擊交互式頁面的叉號,或程序執行完畢調用發起系統調用正常退出,在linux中用exit,在windows中用ExitProcess)
    2. 出錯退出(自願,python a.py中a.py不存在)
    3. 嚴重錯誤(非自願,執行非法指令,如引用不存在的內存,1/0等,能夠捕捉異常,try...except...)
    4. 被其餘進程殺死(非自願,如kill -9)
 3.進程併發的實現(瞭解)
    進程併發的實如今於,硬件中斷一個正在運行的進程,把此時進程運行的全部狀態保存下來,爲此,操做系統維護一張表格,即進程表(process table),每一個進程佔用一個進程表項(這些表項也稱爲進程控制塊)
    
    該表存放了進程狀態的重要信息:程序計數器、堆棧指針、內存分配情況、全部打開文件的狀態、賬號和調度信息,以及其餘在進程由運行態轉爲就緒態或阻塞態時,必須保存的信息,從而保證該進程在再次啓動時,就像從未被中斷過同樣。
 
 
 
 
 
仔細說來,multiprocess不是一個模塊而是python中一個操做、管理進程的包。 之因此叫multi是取自multiple的多功能的意思,在這個包中幾乎包含了和進程有關的全部子模塊。因爲提供的子模塊很是多,爲了方便你們歸類記憶,我將這部分大體分爲四個部分:建立進程部分,進程同步部分,進程池部分,進程之間數據共享。重點強調:進程沒有任何共享狀態,進程修改的數據,改動僅限於該進程內,可是經過一些特殊的方法,能夠實現進程之間數據的共享。
 
1.process模塊介紹
 
        process模塊是一個建立進程的模塊,藉助這個模塊,就能夠完成進程的建立。這個模塊幫咱們建立進程
From Multiprocessing import Process
主進程 當前運行文件
父進程子進程
查看進程ID,
查看父進程 getppid()   pp:parent process
查看本身進程的:getpid() p:process
 
    # 兩種傳參方式:
    def func1(n):
    print('>>>', n)
 
 
    if __name__ == '__main__':
    # p1 = Process(target=func1,args=(1,))
    p1 = Process(target=func1, kwargs={'n': 1})
    p1.start()
    print('主進程結束')
   咱們先寫一個程序來看看:
#當前文件名稱爲test.py
from multiprocessing import Process
 
 
def func():
print(12345)
 
 
if __name__ == '__main__': # windows 下才須要寫這個,這和系統建立進程的機制有關係,不用深究,記着windows下要寫就好啦
# 首先我運行當前這個test.py文件,運行這個文件的程序,那麼就產生了進程,這個進程咱們稱爲主進程
 
p = Process(target=func, ) # 將函數註冊到一個進程中,p是一個進程對象,此時尚未啓動進程,只是建立了一個進程對象。而且func是不加括號的,由於加上括號這個函數就直接運行了對吧。
p.start() # 告訴操做系統,給我開啓一個進程,func這個函數就被咱們新開的這個進程執行了,而這個進程是我主進程運行過程當中建立出來的,
# 因此稱這個新建立的進程爲主進程的子進程,而主進程又能夠稱爲這個新進程的父進程。
# 而這個子進程中執行的程序,至關於將如今這個test.py文件中的程序copy到一個你看不到的python文件中去執行了,就至關於當前這個文件,被另一個py文件import過去並執行了。
# start並非直接就去執行了,咱們知道進程有三個狀態,進程會進入進程的三個狀態,就緒,(被調度,也就是時間片切/換到它的時候)執行,阻塞,而且在這個三個狀態之間不斷的轉換,等待cpu執行時間片到了。
print('*' * 10) # 這是主進程的程序,上面開啓的子進程的程序是和主進程的程序同時運行的,咱們稱爲異步
 
上面說了,咱們經過主進程建立的子進程是異步執行的,那麼咱們就驗證一下,而且看一會兒進程和主進程(也就是父進程)的ID號(講一下pid和ppid,使用pycharm舉例),來看看是不是父子關係。
 
import os
import time
from multiprocessing import Process
 
 
# 父進程:子進程是在父進程的運行過程當中開啓的
# 主進程
 
def func():
print('子進程', os.getpid())
print('子進程的父進程', os.getppid())
print(123)
time.sleep(10)
 
 
if __name__ == '__main__':
print('準備開始其餘進程了')
print('主進程的父進程ID號>>>', os.getppid())
print('主進程的進程ID號>>>', os.getpid())
# 建立一個進程,target:我新建立的這個進程要去執行func1這個函數
p = Process(target=func, )
# 啓動進程
p.start()
print('到這裏結束了')
 
 
給子進程傳爺爺的參數
 
def func(pycharm_id):
print('子進程', os.getpid())
print('子進程的父進程', os.getppid())
print('子進程的爺爺進程的ID(pycharm)', pycharm_id)
print(123)
time.sleep(10)
 
 
if __name__ == '__main__':
print('準備開始其餘進程了')
print('主進程的父進程ID號>>>', os.getppid())
pycharm_id = os.getppid()
print('主進程的進程ID號>>>', os.getpid())
# 建立一個進程,target:我新建立的這個進程要去執行func1這個函數
p = Process(target=func, args=(pycharm_id,))
# 啓動進程
p.start()
func(pycharm_id)
 
print('到這裏結束了')
 
   打開windows下的任務管理器,看pycharm的pid進程號,是咱們上面運行的test.py這個文件主進程的父進程號:
 
    看一個問題,說明linux和windows兩個不一樣的操做系統建立進程的不一樣機制致使的不一樣結果: 
 
 
import time
import os
from multiprocessing import Process
 
def func():
print('aaaa')
time.sleep(1)
print('子進程>>',os.getpid())
print('該子進程的父進程>>',os.getppid())
print(12345)
 
print('太白老司機~~~~') #若是我在這裏加了一個打印,你會發現運行結果中會出現兩次打印出來的太白老司機,由於咱們在主進程中開了一個子進程,子進程中的程序至關於import的主進程中的程序,那麼import的時候會不會執行你import的那個文件的程序啊,前面學的,是會執行的,因此出現了兩次打印
#實際上是由於windows開起進程的機制決定的,在linux下是不存在這個效果的,由於windows使用的是process方法來開啓進程,他就會拿到主進程中的全部程序,而linux下只是去執行我子進程中註冊的那個函數,不會執行別的程序,這也是爲何在windows下要加上執行程序的時候,
要加上if __name__ == '__main__':,不然會出現子進程中運行的時候還開啓子進程,那就出現無限循環的建立進程了,就報錯了
 
一個進程的生命週期:若是子進程的運行時間長,那麼等到子進程執行結束程序才結束,若是主進程的執行時間長,那麼主進程執行結束程序才結束,實際上咱們在子進程中打印的內容是在主進程的執行結果中看不出來的,可是pycharm幫咱們作了優化,由於它會識別到你這是開的子進程,幫你把子進程中打印的內容打印到了顯示臺上。
    若是說一個主進程運行完了以後,咱們把pycharm關了,可是子進程尚未執行結束,那麼子進程還存在嗎?這要看你的進程是如何配置的,若是說咱們沒有配置說我主進程結束,子進程要跟着結束,那麼主進程結束的時候,子進程是不會跟着結束的,他會本身執行完,若是我設定的是主進程結束,子進程必須跟着結束,那麼就不會出現單獨的子進程(孤兒進程)了,具體如何設置,看下面的守護進程的講解。好比說,咱們未來啓動項目的時候,可能經過cmd來啓動,那麼我cmd關閉了你的項目就會關閉嗎,不會的,由於你的項目不能中止對外的服務,對吧。
 
Process類中參數的介紹:
參數介紹:
1 group參數未使用,值始終爲None
2 target表示調用對象,即子進程要執行的任務
3 args表示調用對象的位置參數元組,args=(1,2,'egon',)
4 kwargs表示調用對象的字典,kwargs={'name':'egon','age':18}
5 name爲子進程的名稱
給要執行的函數傳參數:
def func(x, y):
print(x)
time.sleep(1)
print(y)
 
 
if __name__ == '__main__':
p = Process(target=func, args=('姑娘', '來玩啊!')) # 這是func須要接收的參數的傳送方式。
p.start()
print('父進程執行結束!')
 
#執行結果:
#父進程執行結束!
#姑娘
#來玩啊!
 
  Process類中各方法的介紹:
1 p.start():啓動進程,並調用該子進程中的p.run()
2 p.run():進程啓動時運行的方法,正是它去調用target指定的函數,咱們自定義類的類中必定要實現該方法
3 p.terminate():強制終止進程p,不會進行任何清理操做,若是p建立了子進程,該子進程就成了殭屍進程,使用該方法須要特別當心這種狀況。若是p還保存了一個鎖那麼也將不會被釋放,進而致使死鎖
4 p.is_alive():若是p仍然運行,返回True
5 p.join([timeout]):主線程等待p終止(強調:是主線程處於等的狀態,而p是處於運行的狀態)。timeout是可選的超時時間,須要強調的是,p.join只能join住start開啓的進程,而不能join住run開啓的進程
 
 join方法的例子:
    讓主進程加上join的地方等待(也就是阻塞住),等待子進程執行完以後,再繼續往下執行個人主進程,好多時候,咱們主進程須要子進程的執行結果,因此必需要等待。join感受就像是將子進程和主進程拼接起來同樣,將異步改成同步執行。
    join方法的使用:
 
# 驗證join方法
def func(x,y):
print(x)
time.sleep(1)
print(y)
 
if __name__ == '__main__':
 
p = Process(target=func,args=('姑娘','來玩啊!'))
p.start()
print('我這裏是異步的啊!') #這裏相對於子進程仍是異步的
p.join() #只有在join的地方纔會阻塞住,將子進程和主進程之間的異步改成同步
print('父進程執行結束!')
 
#打印結果:我這裏是異步的啊!
姑娘
來玩啊!
父進程執行結束!
 
怎麼樣開啓多個進程呢?for循環。而且我有個需求就是說,全部的子進程異步執行,而後全部的子進程所有執行完以後,我再執行主進程,怎麼搞?看代碼
# for循環在建立進程中的應用
def fun1(n):
time.sleep(1)
print(n)
 
 
if __name__ == '__main__':
pro_list = []
for i in range(10):
p1 = Process(target=fun1, args=(i,))
p1.start()
pro_list.append(p1)
 
for p in pro_list:
p.join()
 
print('主進程結束')
 
Process類中自帶封裝的各屬性的介紹
 
1 p.daemon:默認值爲False,若是設爲True,表明p爲後臺運行的守護進程,當p的父進程終止時,p也隨之終止,而且設定爲True後,p不能建立本身的新進程,必須在p.start()以前設置
2 p.name:進程的名稱
3 p.pid:進程的pid
4 p.exitcode:進程在運行時爲None、若是爲–N,表示被信號N結束(瞭解便可)
5 p.authkey:進程的身份驗證鍵,默認是由os.urandom()隨機生成的32字符的字符串。這個鍵的用途是爲涉及網絡鏈接的底層進程間通訊提供安全性,這類鏈接只有在具備相同的身份驗證鍵時才能成功(瞭解便可)
 
2.Process類的使用
 
Windows下寫代碼開啓子進程時,必須寫上if __name__ == ‘__main__’:
    注意:在windows中Process()必須放到# if __name__ == '__main__':下
 
 
進程的建立第二種方法(繼承)
 
建立進程第二種方法:
        本身定義一個類, 繼承Process類, 必須寫一個run方法, 想傳參數, 自行寫init方法, 而後執行super父類的init方法
class MyProcess(Process):
def __init__(self, n):
super().__init__()
self.n = n
 
def run(self):
print(123)
print('你看看n>>', self.n)
 
 
if __name__ == '__main__':
p = MyProcess(100)
p.start() # 給操做系統發送建立進程的指令,子進程建立好以後,要被執行,執行的時候就會執行run方法
print('主進程結束')
 
 
進程之間的數據是隔離的:
# 進程之間是空間隔離的,不共享資源
global_num = 100
 
 
def func1():
global global_num
global_num = 0
print('子進程全局變量>>>', global_num)
 
 
if __name__ == '__main__':
p1 = Process(target=func1, )
p1.start()
time.sleep(1)
print('主進程的全局變量>>>', global_num)
 
Terminate : 給操做系統發送了一個關閉進程的信號,實際執行是操做系統
  is_alive: 看一下你這個進程對象是否還活着
def func1():
time.sleep(2)
print()
print('子進程')
 
 
if __name__ == '__main__':
p1 = Process(target=func1, )
p1.start()
 
p1.terminate() # 給操做系統發了一個關閉p1子進程的信號,關閉進程
time.sleep(1)
print('進程是否還活着:', p1.is_alive())
print(p1.pid)
print('主進程結束')
 
 
name與pid
 
class Piao(Process):
def __init__(self, name):
# self.name=name
# super().__init__() #Process的__init__方法會執行self.name=Piao-1,
# #因此加到這裏,會覆蓋咱們的self.name=name
 
# 爲咱們開啓的進程設置名字的作法
super().__init__()
self.name = name
 
def run(self):
print('%s is piaoing' % self.name)
time.sleep(random.randrange(1, 3))
print('%s is piao end' % self.name)
 
 
p = Piao('egon')
p.start()
print('開始')
print(p.pid) # 查看pid
 
 
    殭屍進程與孤兒進程(簡單瞭解 一下就能夠啦)
 
一:殭屍進程(有害)
  殭屍進程:一個進程使用fork建立子進程,若是子進程退出,而父進程並無調用wait或waitpid獲取子進程的狀態信息,那麼子進程的進程描述符仍然保存在系統中。這種進程稱之爲僵死進程。詳解以下
 
咱們知道在unix/linux中,正常狀況下子進程是經過父進程建立的,子進程在建立新的進程。子進程的結束和父進程的運行是一個異步過程,即父進程永遠沒法預測子進程到底何時結束,若是子進程一結束就馬上回收其所有資源,那麼在父進程內將沒法獲取子進程的狀態信息。
 
所以,UNⅨ提供了一種機制能夠保證父進程能夠在任意時刻獲取子進程結束時的狀態信息:
一、在每一個進程退出的時候,內核釋放該進程全部的資源,包括打開的文件,佔用的內存等。可是仍然爲其保留必定的信息(包括進程號the process ID,退出狀態the termination status of the process,運行時間the amount of CPU time taken by the process等)
二、直到父進程經過wait / waitpid來取時才釋放. 但這樣就致使了問題,若是進程不調用wait / waitpid的話,那麼保留的那段信息就不會釋放,其進程號就會一直被佔用,可是系統所能使用的進程號是有限的,若是大量的產生僵死進程,將由於沒有可用的進程號而致使系統不能產生新的進程. 此即爲殭屍進程的危害,應當避免。
 
  任何一個子進程(init除外)在exit()以後,並不是立刻就消失掉,而是留下一個稱爲殭屍進程(Zombie)的數據結構,等待父進程處理。這是每一個子進程在結束時都要通過的階段。若是子進程在exit()以後,父進程沒有來得及處理,這時用ps命令就能看到子進程的狀態是「Z」。若是父進程能及時 處理,可能用ps命令就來不及看到子進程的殭屍狀態,但這並不等於子進程不通過殭屍狀態。  若是父進程在子進程結束以前退出,則子進程將由init接管。init將會以父進程的身份對殭屍狀態的子進程進行處理。
 
二:孤兒進程(無害)
 
  孤兒進程:一個父進程退出,而它的一個或多個子進程還在運行,那麼那些子進程將成爲孤兒進程。孤兒進程將被init進程(進程號爲1)所收養,並由init進程對它們完成狀態收集工做。
 
  孤兒進程是沒有父進程的進程,孤兒進程這個重任就落到了init進程身上,init進程就好像是一個民政局,專門負責處理孤兒進程的善後工做。每當出現一個孤兒進程的時候,內核就把孤 兒進程的父進程設置爲init,而init進程會循環地wait()它的已經退出的子進程。這樣,當一個孤兒進程淒涼地結束了其生命週期的時候,init進程就會表明黨和政府出面處理它的一切善後工做。所以孤兒進程並不會有什麼危害。
 
咱們來測試一下(建立完子進程後,主進程所在的這個腳本就退出了,當父進程先於子進程結束時,子進程會被init收養,成爲孤兒進程,而非殭屍進程),文件內容
 
import os
import sys
import time
 
pid = os.getpid()
ppid = os.getppid()
print 'im father', 'pid', pid, 'ppid', ppid
pid = os.fork()
#執行pid=os.fork()則會生成一個子進程
#返回值pid有兩種值:
#    若是返回的pid值爲0,表示在子進程當中
#    若是返回的pid值>0,表示在父進程當中
if pid > 0:
    print 'father died..'
    sys.exit(0)
 
# 保證主線程退出完畢
time.sleep(1)
print 'im child', os.getpid(), os.getppid()
 
執行文件,輸出結果:
im father pid 32515 ppid 32015
father died..
im child 32516 1
 
看,子進程已經被pid爲1的init進程接收了,因此殭屍進程在這種狀況下是不存在的,存在只有孤兒進程而已,孤兒進程聲明週期結束天然會被init來銷燬。
 
 
三:殭屍進程危害場景:
 
  例若有個進程,它按期的產 生一個子進程,這個子進程須要作的事情不多,作完它該作的事情以後就退出了,所以這個子進程的生命週期很短,可是,父進程只管生成新的子進程,至於子進程 退出以後的事情,則一律漠不關心,這樣,系統運行上一段時間以後,系統中就會存在不少的僵死進程,假若用ps命令查看的話,就會看到不少狀態爲Z的進程。 嚴格地來講,僵死進程並非問題的根源,罪魁禍首是產生出大量僵死進程的那個父進程。所以,當咱們尋求如何消滅系統中大量的僵死進程時,答案就是把產生大 量僵死進程的那個元兇槍斃掉(也就是經過kill發送SIGTERM或者SIGKILL信號啦)。槍斃了元兇進程以後,它產生的僵死進程就變成了孤兒進 程,這些孤兒進程會被init進程接管,init進程會wait()這些孤兒進程,釋放它們佔用的系統進程表中的資源,這樣,這些已經僵死的孤兒進程 就能瞑目而去了。
 
四:測試
#一、產生殭屍進程的程序test.py內容以下
 
#coding:utf-8
from multiprocessing import Process
import time,os
 
def run():
    print('子',os.getpid())
 
if __name__ == '__main__':
    p=Process(target=run)
    p.start()
    
    print('主',os.getpid())
    time.sleep(1000)
 
 
#二、在unix或linux系統上執行
[root@vm172-31-0-19 ~]# python3  test.py &
[1] 18652
[root@vm172-31-0-19 ~]# 主 18652
子 18653
 
[root@vm172-31-0-19 ~]# ps aux |grep Z
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root     18653  0.0  0.0      0     0 pts/0    Z    20:02   0:00 [python3] <defunct> #出現殭屍進程
root     18656  0.0  0.0 112648   952 pts/0    S+   20:02   0:00 grep --color=auto Z
 
[root@vm172-31-0-19 ~]# top #執行top命令發現1zombie
top - 20:03:42 up 31 min,  3 users,  load average: 0.01, 0.06, 0.12
Tasks:  93 total,   2 running,  90 sleeping,   0 stopped,   1 zombie
%Cpu(s):  0.0 us,  0.3 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1016884 total,    97184 free,    70848 used,   848852 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   782540 avail Mem
 
  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                                        
root      20   0   29788   1256    988 S  0.3  0.1   0:01.50 elfin                                                                                                                      
 
 
#三、
等待父進程正常結束後會調用wait/waitpid去回收殭屍進程
但若是父進程是一個死循環,永遠不會結束,那麼該殭屍進程就會一直存在,殭屍進程過多,就是有害的
解決方法一:殺死父進程
解決方法二:對開啓的子進程應該記得使用join,join會回收殭屍進程
參考python2源碼註釋
class Process(object):
    def join(self, timeout=None):
        '''
        Wait until child process terminates
        '''
        assert self._parent_pid == os.getpid(), 'can only join a child process'
        assert self._popen is not None, 'can only join a started process'
        res = self._popen.wait(timeout)
        if res is not None:
            _current_process._children.discard(self)
 
join方法中調用了wait,告訴系統釋放殭屍進程。discard爲從本身的children中剔除
  
殭屍進程與孤兒進程
 
3.守護進程
 
    以前咱們講的子進程是不會隨着主進程的結束而結束,子進程所有執行完以後,程序才結束,那麼若是有一天咱們的需求是個人主進程結束了,由我主進程建立的那些子進程必須跟着結束,怎麼辦?守護進程就來了!
    主進程建立守護進程
      其一:守護進程會在主進程代碼執行結束後就終止
      其二:守護進程內沒法再開啓子進程,不然拋出異常:AssertionError: daemonic processes are not allowed to have children
    注意:進程之間是互相獨立的,主進程代碼運行結束,守護進程隨即終止
 
 
class Myprocess(Process):
def __init__(self, person):
super().__init__()
self.person = person
 
def run(self):
print(os.getpid(), self.name)
print('%s正在和女主播聊天' % self.person)
time.sleep(3)
 
 
if __name__ == '__main__':
p = Myprocess('太白')
p.daemon = True # 必定要在p.start()前設置,設置p爲守護進程,禁止p建立子進程,而且父進程代碼執行結束,p即終止運行
p.start()
# time.sleep(1) # 在sleep時linux下查看進程id對應的進程ps -ef|grep id
print('主')
 
 
 
進程建立的兩種方法
Process()
繼承Process
重寫run方法,傳參數的時候要寫init,可是注意要在init方法中運行父類的init方法
Windows下寫代碼開啓子進程時,必須寫上if __name__ == ‘__main__’:
兩種參數形式
Args=(1,)
Kwargs = {‘n’:1} n必須和執行任務須要的形參相同
驗證空間隔離
對全局變量進行修改
Join
等待子進程運行結束,主進程再繼續往下執行
Terminate : 給操做系統發送了一個關閉進程的信號,實際執行是操做系統
  is_alive: 看一下你這個進程對象是否還活着
建立進程的for循環應用
測試了一下三個任務的執行時間
子進程不能input
殭屍進程和孤兒進程
守護進程
1 建立進程的兩種方法
直接使用from multiprocessing import Process
自定義一個類, 繼承Process類, 重寫run方法, 若是須要傳參, 重寫init, 並調用super執行父類的init
2兩種傳參方式:
Args = (1,)元組
Kwargs = {‘n’:1,}
3驗證進程之間是空間隔離的
全局變量
4join等待子進程結束, 而後繼續往下執行,
 
5驗證了一下併發的執行時間
 
6for循環在多進程中的應用
 
7terminate 關閉進程, 可是他只是給操做系統發送一個關閉進程的信號, 實際操做是操做系統關閉的.
8is_alive 查看進程是否還活着
9殭屍進程和孤兒進程
10守護進程
P1.Daemon = True
11子進程不能input
 
12__name__ == ‘__main__’ windows下
 
 
 
 
將異步改成同步
同步效率低,可是保證了數據安全    重點
 
給鑰匙}
      with lock(鎖)==========開加還      
還鑰匙}
 
 
編寫了一個搶票
import time
import random
from multiprocessing import Process, Lock
import json
 
 
def get_ticket(i, ticket_lock):
# 全部進程異步執行,到這裏等待,同時再去搶下面的代碼執行
time.sleep(2)
ticket_lock.acquire() # 這裏有個門,只有一我的可以搶到這個鑰匙,加鎖
with open('ticket', 'r') as f:
# 將文件數據load爲字典類型的數據
last_ticket_info = json.load(f)
# 查看一下餘票信息
last_ticket = last_ticket_info['count']
# 若是看到餘票大於0,說明你能夠搶到票
if last_ticket > 0:
# 模擬網絡延遲時間
time.sleep(random.random())
# 搶到一張票就減去
last_ticket -= 1
last_ticket_info['count'] = last_ticket
# 將修改後的餘票寫回文件
with open('ticket', 'w') as f:
# 經過json.dump方法來寫回文件,字符串的形式
json.dump(last_ticket_info, f)
print('%s搶到了' % i)
else:
print("%s沒票" % i)
# 釋放鎖,也就是還鑰匙的操做
ticket_lock.release()
 
 
if __name__ == '__main__':
# 建立一個鎖
ticket_lock = Lock()
for i in range(10):
# 將鎖做爲參數傳給每一個進程,由於每一個進程都須要經過所來進行限制,同步
p = Process(target=get_ticket, args=(i, ticket_lock,))
p.start()
 
 
0搶到了
1沒票
6沒票
3沒票
2沒票
9沒票
8沒票
5沒票
4沒票
7沒票
 
 
#加鎖能夠保證多個進程修改同一塊數據時,同一時間只能有一個任務能夠進行修改,即串行的修改,沒錯,速度是慢了,但犧牲了速度卻保證了數據安全。雖然能夠用文件共享數據實現進程間通訊,但問題是:
1.效率低(共享數據基於文件,而文件是硬盤上的數據)
2.須要本身加鎖處理
 
#所以咱們最好找尋一種解決方案可以兼顧:一、效率高(多個進程共享一塊內存的數據)二、幫咱們處理好鎖問題。這就是mutiprocessing模塊爲咱們提供的基於消息的IPC通訊機制:隊列和管道。隊列和管道都是將數據存放於內存中
隊列又是基於(管道+鎖)實現的,可讓咱們從複雜的鎖問題中解脫出來,
咱們應該儘可能避免使用共享數據,儘量使用消息傳遞和隊列,避免處理複雜的同步和鎖問題,並且在進程數目增多時,每每能夠得到更好的可獲展性。
 
IPC通訊機制(瞭解):IPC是intent-Process Communication的縮寫,含義爲進程間通訊或者跨進程通訊,是指兩個進程之間進行數據交換的過程。IPC不是某個系統所獨有的,任何一個操做系統都須要有相應的IPC機制,
好比Windows上能夠經過剪貼板、管道和郵槽等來進行進程間通訊,而Linux上能夠經過命名共享內容、信號量等來進行進程間通訊。Android它也有本身的進程間通訊方式,Android建構在Linux基礎上,繼承了一
部分Linux的通訊方式。
 
 
互斥鎖同時只容許一個線程更改數據,而信號量Semaphore是同時容許必定數量的線程更改數據 。
假設商場裏有4個迷你唱吧,因此同時能夠進去4我的,若是來了第五我的就要在外面等待,等到有人出來才能再進去玩。
實現:
信號量同步基於內部計數器,每調用一次acquire(),計數器減1;每調用一次release(),計數器加1.當計數器爲0時,acquire()調用被阻塞。這是迪科斯徹(Dijkstra)信號量概念P()和V()的Python實現。信號量同步機制適用於訪問像服務器這樣的有限資源。
信號量與進程池的概念很像,可是要區分開,信號量涉及到加鎖的概念
          好比大保健:提早設定好,一個房間只有4個牀(計數器如今爲4),那麼同時只能四我的進來,誰先來的誰先佔一個牀(acquire,計數器減1),4個牀滿了以後(計數器爲0了),第五我的就要等着,等其中一我的出來(release,計數器加1),他就去佔用那個牀了。
    信號量使用:
from multiprocessing import Process, Semaphore
import time
 
 
def func(i, s):
s.acquire()
print('%s男豬腳' % i)
 
time.sleep(0.5)
print('-------------')
s.release()
 
 
if __name__ == '__main__':
s = Semaphore(4) # 建立一個計數器,每次acquire就減1,直到減到0,那麼上面的任務只有4個在同時異步的執行,後面的進程須要等待.
for i in range(10):
p = Process(target=func, args=(i, s,))
p.start()
 
 
0男豬腳
2男豬腳
1男豬腳
5男豬腳
8男豬腳
-------------
6男豬腳
-------------
3男豬腳
-------------
7男豬腳
-------------
4男豬腳
-------------
9男豬腳
-------------
-------------
-------------
-------------
-------------
 
 
 
python線程的事件用於主線程控制其餘線程的執行,事件主要提供了三個方法 set、wait、clear。
 
事件處理的機制:全局定義了一個「Flag」,若是「Flag」值爲 False,那麼當程序執行 event.wait 方法時就會阻塞,若是「Flag」值爲True,那麼event.wait 方法時便再也不阻塞。
 
clear:將「Flag」設置爲False
set:將「Flag」設置爲True
set和clear 修改事件的狀態 set-->True clear-->False
is_set 用來查看一個事件的狀態
wait 依據事件的狀態來決定是否阻塞 False-->阻塞 True-->不阻塞
 
from multiprocessing import Process, Event
 
e = Event()
print(e.is_set()) # 查看一個事件的狀態,默認爲False,可經過set方法改成True
print('look,here!')
e.set() # 將is_set()的狀態改成True
print(e.is_set()) # is_set()查看一個事件的狀態,默認爲False,可經過set方法改成True
e.clear() # 將is_set()的狀態改成False
print(e.is_set()) # is_set()查看一個事件的狀態,默認爲False,可經過set方法改成True
e.wait() # 根據is_set()的狀態決定是否在這阻塞住,is_set()=False哪麼就阻塞,is_set()=True就不阻塞
print('give me')
 
 
模擬紅綠燈執行狀態的函數:
 
from multiprocessing import Process, Event
import time
 
 
def hld(e):
while 1:
print("紅燈")
time.sleep(6)
e.set()
print('綠燈')
time.sleep(2)
e.clear()
 
 
def car(i, e):
if not e.is_set(): # 新來的車看到的是紅燈
print('等.......')
e.wait()
print("走")
else:
print('直接走!!!')
 
 
if __name__ == '__main__':
e = Event()
 
hl = Process(target=hld, args=(e,))
hl.start()
while 1:
time.sleep(0.86)
# 建立2輛車
for i in range(2):
p = Process(target=car, args=(i, e))
p.start()
 
 
紅燈
等.......
等.......
等.......
等.......
等.......
等.......
等.......
等.......
等.......
等.......
等.......
等.......
綠燈
直接走!!!
直接走!!!
直接走!!!
直接走!!!
直接走!!!
直接走!!!
紅燈
等.......
等.......
等.......
等.......
等.......
等.......
等.......
等.......
等.......
等.......
等.......
等.......
等.......
等.......
綠燈
直接走!!!
直接走!!!
 
 
 
 
5.隊列(推薦使用)   
    進程彼此之間互相隔離,要實現進程間通訊(IPC),multiprocessing模塊支持兩種形式:隊列和管道,這兩種方式都是使用消息傳遞的。隊列就像一個特殊的列表,可是能夠設置固定長度,而且從前面插入數據,從後面取出數據,先進先出。 
   隊列是進程安全的 :同一時間只能一個進程拿到隊列中的一個數據,你拿到了一個數據,這個數據別人就拿不到了。 
 
 
q = Queue([maxsize])
建立共享的進程隊列。maxsize是隊列中容許的最大項數。若是省略此參數,則無大小限制。底層隊列使用管道和鎖定實現。另外,還須要運行支持線程以便隊列中的數據傳輸到底層管道中。
Queue的實例q具備如下方法:
 
q.get( [ block [ ,timeout ] ] )
返回q中的一個項目。若是q爲空,此方法將阻塞,直到隊列中有項目可用爲止。block用於控制阻塞行爲,默認爲True. 若是設置爲False,將引起Queue.Empty異常(定義在Queue模塊中)。timeout是可選超時時間,用在阻塞模式中。若是在制定的時間間隔內沒有項目變爲可用,將引起Queue.Empty異常。
 
  q.get_nowait ( )
同q.get(False)方法。
 
  q.put  (item [, block [,timeout ] ] )
將item放入隊列。若是隊列已滿,此方法將阻塞至有空間可用爲止。block控制阻塞行爲,默認爲True。若是設置爲False,將引起Queue.Empty異常(定義在Queue庫模塊中)。timeout指定在阻塞模式中等待可用空間的時間長短。超時後將引起Queue.Full異常。
 
  q.qsize ()
返回隊列中目前項目的正確數量。此函數的結果並不可靠,由於在返回結果和在稍後程序中使用結果之間,隊列中可能添加或刪除了項目。在某些系統上,此方法可能引起NotImplementedError異常。
 
 
  q.empty ()
若是調用此方法時 q爲空,返回True。若是其餘進程或線程正在往隊列中添加項目,結果是不可靠的 。也就是說,在返回和使用結果之間,隊列中可能已經加入新的項目。
 
 q.full  ()
若是q已滿,返回爲True. 因爲線程的存在,結果也多是不可靠的(參考q.empty()方法)。。
 
from multiprocessing import Process, Queue
 
# 先進先出
q = Queue(3)
q.put(1)
q.put(2)
# print(q.full())#q.full()隊列滿返回True,不滿返回False
q.put(3)
# print('>>>>', q.full())
 
# q.put(4) # 超出了隊列長度,你put插入數據的時候會阻塞
print(q.get())
print(".......", q.empty())
# print(q.get())
# print(q.get())
print('>>>>', q.empty()) # 不可信,隊列空了返回True,不空返回False
# print(q.get()) # 隊列爲空的時候,get會阻塞
print(q.get_nowait()) # q.get_nowait()等於q.get(False)
print(q.get(False))
 
try:
q.get(False)
q.get_nowait()
except:
print("如今是空的")
 
while 1:
try:
q.get(False)
except:
print("如今是空的")
break
 
 
 
  子進程與父進程經過隊列進行通訊 
 
def boy(q):
q.put("1111")
 
 
def girl(q):
print(q.get())
print("主進程", q.get())
 
 
if __name__ == '__main__':
q = Queue(5)
b = Process(target=boy, args=(q,))
g = Process(target=girl, args=(q,))
g.start()
b.start()
time.sleep(1)
q.put("222222")
 
 
 
下面咱們來看一個叫作生產者消費者模型的東西:
      在併發編程中使用生產者和消費者模式可以解決絕大多數併發問題。 該模式經過平衡生產線程和消費線程的工做能力來提升程序的總體處理數據的速度。    
爲何要使用生產者和消費者模式
      在線程世界裏,生產者就是生產數據的線程,消費者就是消費數據的線程。在多線程開發當中,若是生產者處理速度很快,而消費者處理速度很慢,那麼生產者就必須等待消費者處理完,才能繼續生產數據。一樣的道理,若是消費者的處理能力大於生產者,那麼消費者就必須等待生產者。爲了解決這個問題因而引入了生產者和消費者模式。
什麼是生產者消費者模式
      生產者消費者模式是經過一個容器來解決生產者和消費者的強耦合問題。生產者和消費者彼此之間不直接通信,而經過阻塞隊列來進行通信,因此生產者生產完數據以後不用等待消費者處理,直接扔給阻塞隊列,消費者不找生產者要數據,而是直接從阻塞隊列裏取,阻塞隊列就至關於一個緩衝區,平衡了生產者和消費者的處理能力,而且我能夠根據生產速度和消費速度來均衡一下多少個生產者能夠爲多少個消費者提供足夠的服務,就能夠開多進程等等,而這些進程都是到阻塞隊列或者說是緩衝區中去獲取或者添加數據。
 
那麼咱們基於隊列來實現一個生產者消費者模型,代碼示例:
# 生產者消費者模型
import time
from multiprocessing import Process, Queue
 
 
def scz(q):
for i in range(1, 11):
time.sleep(1)
print("生產了>>", i)
q.put(i)
q.put(None) # 針對第三個版本的消費者,往隊列裏面加了一個結束信號
 
 
# 版本1
def xfz(q):
while 1:
time.sleep(2)
s = q.get()
print("消費者吃了", s)
 
# 生產了>> 1
# 消費者吃了 1
# 生產了>> 2
# 生產了>> 3
# 消費者吃了 2
# 生產了>> 4
# 生產了>> 5
# 消費者吃了 3
# 生產了>> 6
# 生產了>> 7
# 消費者吃了 4
# 生產了>> 8
# 生產了>> 9
# 消費者吃了 5
# 生產了>> 10
# 消費者吃了 6
# 消費者吃了 7
# 消費者吃了 8
# 消費者吃了 9
# 消費者吃了 10
 
 
# 版本2
def xfz(q):
while 1:
time.sleep(0.5)
try:
s = q.get(False)
print("消費者吃了", s)
except:
break
 
 
# 生產了>> 1
# 生產了>> 2
# 生產了>> 3
# 生產了>> 4
# 生產了>> 5
# 生產了>> 6
# 生產了>> 7
# 生產了>> 8
# 生產了>> 9
# 生產了>> 10
 
# 版本3
def xfz(q):
while 1:
time.sleep(2)
s = q.get()
if s == None:
break
else:
print("消費者吃了", s)
 
# 生產了>> 1
# 消費者吃了 1
# 生產了>> 2
# 生產了>> 3
# 消費者吃了 2
# 生產了>> 4
# 生產了>> 5
# 消費者吃了 3
# 生產了>> 6
# 生產了>> 7
# 消費者吃了 4
# 生產了>> 8
# 生產了>> 9
# 消費者吃了 5
# 生產了>> 10
# 消費者吃了 6
# 消費者吃了 7
# 消費者吃了 8
# 消費者吃了 9
# 消費者吃了 10
 
 
if __name__ == '__main__':
# 經過隊列來模擬緩衝區, 大小設置爲20
q = Queue(20)
# 生產者進程
scz_p = Process(target=scz, args=(q,))
scz_p.start()
# 消費者進程
xfz_p = Process(target=xfz, args=(q,))
xfz_p.start()
 
 
#生產者消費者模型總結
 
#程序中有兩類角色 一類負責生產數據(生產者)
一類負責處理數據(消費者)
#引入生產者消費者模型爲了解決的問題是: 平衡生產者與消費者之間的工做能力,從而提升程序總體處理數據的速度
#如何實現:
生產者<-->隊列<——>消費者
#生產者消費者模型實現類程序的解耦和
 
  生產者消費者模型主進程發送結束信號:  
 
import time
from multiprocessing import Process, Queue
 
 
def scz(q):
for i in range(1, 11):
time.sleep(1)
print("生產了>>", i)
q.put(i)
 
 
def xfz(q):
while 1:
time.sleep(2)
s = q.get()
if s == None:
break
else:
print('消費者吃了', s)
 
 
if __name__ == '__main__':
q = Queue(20)
scz_p = Process(target=scz, args=(q,))
scz_p.start()
xfz_p = Process(target=xfz, args=(q,))
xfz_p.start()
scz_p.join()
 
q.put(None)
 
 
# 生產了>> 1
# 生產了>> 2
# 消費者吃了 1
# 生產了>> 3
# 生產了>> 4
# 消費者吃了 2
# 生產了>> 5
# 生產了>> 6
# 消費者吃了 3
# 生產了>> 7
# 消費者吃了 4
# 生產了>> 8
# 生產了>> 9
# 消費者吃了 5
# 生產了>> 10
# 消費者吃了 6
# 消費者吃了 7
# 消費者吃了 8
# 消費者吃了 9
# 消費者吃了 10
 
 
 
#JoinableQueue([maxsize]):這就像是一個Queue對象,但隊列容許項目的使用者通知生成者項目已經被成功處理。通知進程是使用共享的信號和條件變量來實現的。
 
#參數介紹: maxsize是隊列中容許最大項數,省略則無大小限制。
  #方法介紹: JoinableQueue的實例p除了與Queue對象相同的方法以外還具備:
q.task_done():使用者使用此方法發出信號,表示q.get()的返回項目已經被處理。若是調用此方法的次數大於從隊列中刪除項目的數量,將引起ValueError異常
q.join():生產者調用此方法進行阻塞,直到隊列中全部的項目均被處理。阻塞將持續到隊列中的每一個項目均調用q.task_done()方法爲止,也就是隊列中的數據所有被get拿走了。
 
 
#生產者消費者模型
import time
from multiprocessing import Process,Queue,JoinableQueue
 
def producer(q):
for i in range(1,11):
time.sleep(0.5)
print('生產了包子%s號' % i)
q.put(i)
q.join()
print('在這裏等你')
def consumer(q):
while 1:
time.sleep(1)
s = q.get()
print('消費者吃了%s包子' % s)
q.task_done() #給q對象發送一個任務結束的信號
 
if __name__ == '__main__':
#經過隊列來模擬緩衝區,大小設置爲20
q = JoinableQueue(20)
#生產者進程
pro_p = Process(target=producer,args=(q,))
pro_p.start()
#消費者進程
con_p = Process(target=consumer,args=(q,))
con_p.daemon = True #
con_p.start()
pro_p.join()
print('主進程結束')
 
生產了包子1號
生產了包子2號
消費者吃了1包子
生產了包子3號
生產了包子4號
消費者吃了2包子
生產了包子5號
生產了包子6號
消費者吃了3包子
生產了包子7號
生產了包子8號
消費者吃了4包子
生產了包子9號
生產了包子10號
消費者吃了5包子
消費者吃了6包子
消費者吃了7包子
消費者吃了8包子
消費者吃了9包子
消費者吃了10包子
在這裏等你
主進程結束
 
 
 
 
 
 
6.管道(瞭解)
管道的數據不安全:
管道 : Pipe ,recv消息的時候 OSError,EOFError,數據不安全的
    進程間通訊(IPC)方式二:管道(不推薦使用,瞭解便可),會致使數據不安全的狀況出現,後面咱們會說到爲何會帶來數據 不安全的問題。
 
 
conn1, conn2 = Pipe()
conn1.send('你好')
print('>>>>>>>>>')
msg = conn2.recv()
print(msg)
 
#建立管道的類:
Pipe()
在進程之間建立一條管道,並返回元組(conn1,conn2),其中conn1,conn2表示管道兩端的鏈接對象,強調一點:必須在產生Process對象以前產生管道
#主要方法:
conn1.recv():接收conn2.send(obj)發送的對象。若是沒有消息可接收,recv方法會一直阻塞。若是鏈接的另一端已經關閉,那麼recv方法會拋出EOFError。
conn1.send(obj):經過鏈接發送對象。obj是與序列化兼容的任意對象
#其餘方法:conn1.close():關閉鏈接。若是conn1被垃圾回收,將自動調用此方法
 
from multiprocessing import Process, Pipe
 
def f(conn):
conn.send("Hello 妹妹") #子進程發送了消息 conn.close()
 
if __name__ == '__main__':
parent_conn, child_conn = Pipe() #創建管道,拿到管道的兩端,雙工通訊方式,兩端均可以收發消息
p = Process(target=f, args=(child_conn,)) #將管道的一段給子進程
p.start() #開啓子進程
print(parent_conn.recv()) #主進程接受了消息
p.join()
 
 應該特別注意管道端點的正確管理問題。若是是生產者或消費者中都沒有使用管道的某個端點,就應將它關閉。這也說明了爲什麼在生產者中關閉了管道的輸出端,在消費者中關閉管道的輸入端。若是忘記執行這些步驟,程序可能在消費者中的recv()操做上掛起(就是阻塞)。管道是由操做系統進行引用計數的,必須在全部進程中關閉管道的相同一端就會能生成EOFError異常。所以,在生產者中關閉管道不會有任何效果,除非消費者也關閉了相同的管道端點。 
 
  
主進程將管道的兩端都傳送給子進程,子進程和主進程共用管道的兩種報錯狀況,都是在recv接收的時候報錯的:
    1.主進程和子進程中的管道的相同一端都關閉了,出現EOFError;
    2.若是你管道的一端在主進程和子進程中都關閉了,可是你還用這個關閉的一端去接收消息,那麼就會出現OSError;
 
    因此你關閉管道的時候,就容易出現問題,須要將全部只用這個管道的進程中的兩端所有關閉才行。固然也能夠經過異常捕獲(try:except EOFerror)來處理。
    雖然咱們在主進程和子進程中都打印了一下conn1一端的對象,發現兩個再也不同一個地址,可是子進程中的管道和主進程中的管道仍是能夠通訊的,由於管道是同一套,系統可以記錄。    
 
    咱們的目的就是關閉全部的管道,那麼主進程和子進程進行通訊的時候,能夠給子進程傳管道的一端就夠了,而且用咱們以前學到的,信息發送完以後,再發送一個結束信號None,那麼你收到的消息爲None的時候直接結束接收或者說結束循環,就不用每次都關閉各個進程中的管道了
 
 
 
def func(conn2):
try: # EOFError
msg = conn2.recv()
print(msg)
msg2 = conn2.recv()
 
except EOFError:
print('對方已關閉端口')
 
 
if __name__ == '__main__':
conn1, conn2 = Pipe()
p = Process(target=func, args=(conn2,))
p.start()
conn1.send("123")
conn1.close()
#123
# 對方已關閉端口
 
 
def func(conn1,conn2):
msg = conn2.recv() #阻塞
print(msg)
 
 
if __name__ == '__main__':
conn1, conn2 = Pipe()
p = Process(target=func, args=(conn1,conn2,))
p.start()
conn1.send('jkl')
conn1.close()
conn1.recv() #OSError: handle is closed
 
 
 
Manager數據共享,也是數據不安全的
Manager : 共享數據 , 數據不安全 , 加鎖
 
展望將來,基於消息傳遞的併發編程是大勢所趨
    即使是使用線程,推薦作法也是將程序設計爲大量獨立的線程集合
    經過消息隊列交換數據。這樣極大地減小了對使用鎖定和其餘同步手段的需求,還能夠擴展到分佈式系統中
    進程間應該儘可能避免通訊,即使須要通訊,也應該選擇進程安全的工具來避免加鎖帶來的問題,應該儘可能避免使用本節所講的共享數據的方式,之後咱們會嘗試使用數據庫來解決進程之間的數據共享問題。
    進程之間數據共享的模塊之一Manager模塊:
 
數據能夠修改:
from multiprocessing import Process, Manager, Lock
 
 
def func(m_dic):
m_dic['name'] = 'maiyaode'
 
 
if __name__ == '__main__':
m = Manager()
m_dic = m.dict({'name': 'liuwie'})
print(m_dic)
p = Process(target=func, args=(m_dic,))
p.start()
p.join()
print(m_dic)
 
 
 多進程共同去處理共享數據的時候,就和咱們多進程同時去操做一個文件中的數據是同樣的,不加鎖就會出現錯誤的結果,進程不安全的,因此也須要加鎖
 
多個進程修改數據:(加鎖)
 
def fun(m_dic, ml):
# with ml:
# m_dic['count'] += 1
m_dic['count'] += 1
 
 
if __name__ == '__main__':
m = Manager()
m_dic = m.dict({"count": 100})
ml = Lock()
p_lis = []
for i in range(10):
p = Process(target=fun, args=(m_dic, ml,))
p.start()
p_lis.append(p)
[pp.join() for pp in p_lis]
print(m_dic)
 
 
 
總結一下,進程之間的通訊:隊列、管道、數據共享也算
下面要講的信號量和事件也至關於鎖,也是全局的,全部進程都能拿到這些鎖的狀態,進程之間這些鎖啊信號量啊事件啊等等的通訊,其實底層仍是socekt,只不過是基於文件的socket通訊,而不是跟上面的數據共享啊空間共享啊之類的機制,咱們以前學的是基於網絡的socket通訊,還記得socket的兩個家族嗎,一個文件的一個網絡的,因此未來若是說這些鎖之類的報錯,可能你看到的就是相似於socket的錯誤,簡單知道一下就能夠啦~~~
工做中經常使用的是鎖,信號量和事件不經常使用,可是信號量和事件面試的時候會問到,你能知道就行啦~~~
 
 
 爲何要有進程池?進程池的概念。
  在程序實際處理問題過程當中,忙時會有成千上萬的任務須要被執行,閒時可能只有零星任務。那麼在成千上萬個任務須要被執行的時候,咱們就須要去建立成千上萬個進程麼?首先,建立進程須要消耗時間,銷燬進程(空間,變量,文件信息等等的內容)也須要消耗時間。第二即使開啓了成千上萬的進程,操做系統也不能讓他們同時執行,維護一個很大的進程列表的同時,調度的時候,還須要進行切換而且記錄每一個進程的執行節點,也就是記錄上下文(各類變量等等亂七八糟的東西,雖然你看不到,可是操做系統都要作),這樣反而會影響程序的效率。所以咱們不能無限制的根據任務開啓或者結束進程。就看咱們上面的一些代碼例子,你會發現有些程序是否是執行的時候比較慢纔出結果,就是這個緣由,那麼咱們要怎麼作呢?
 
  在這裏,要給你們介紹一個進程池的概念,定義一個池子,在裏面放上固定數量的進程,有需求來了,就拿一個池中的進程來處理任務,等處處理完畢,進程並不關閉,而是將進程再放回進程池中繼續等待任務。若是有不少任務須要執行,池中的進程數量不夠,任務就要等待以前的進程執行任務完畢歸來,拿到空閒進程才能繼續執行。也就是說,池中進程的數量是固定的,那麼同一時間最多有固定數量的進程在運行。這樣不會增長操做系統的調度難度,還節省了開閉進程的時間,也必定程度上可以實現併發效果
 
multiprocess.Pool模塊 
 
 
進程池 :(裏面的方法map,同步,異步,join,close)
    P = Pool()
    Map(function,iter-obj)  #異步,自帶join: 主進程等待map裏面任務結束
    Apply : 同步執行,串行,一個完了一個的執行,效率低, 直接就能拿到返回值
    Apply_async() : 異步執行任務,異步的最大量是你進程池中進程的個數
    Join:等待進程池中的任務所有執行完畢
    Close:因爲咱們想看看進程池中現有的任務是否所有運行完畢,或者說我想獲取進程池中當前全部任務的結果,咱們須要等待所有任務執行完畢,可是可能會有新的任務進來,因此沒辦法肯定我現有的任務是多少,我要等他完,可是有新任務一直進來就完不了,全部咱們要鎖定進程池,不讓其餘任務進來.使用close
 
進程池map傳參:
from multiprocessing import Process, Pool
 
 
def func(n):
print(n)
 
 
if __name__ == '__main__':
pool = Pool(4)
# pool.map(func,range(100)) #參數是可迭代的
pool.map(func, ['sb', (1, 2)]) # 參數是可迭代的
    
 
驗證一下傳參的時間
import time
from multiprocessing import Process, Pool
 
 
def func(n):
for i in range(5):
n = n + i
print(n)
 
 
if __name__ == '__main__':
# 驗證一下傳參
pool_start_time = time.time()
pool = Pool(4)
pool.map(func, range(100)) # map自帶join功能,異步執行任務,參數是可迭代的
 
pool_end_time = time.time()
pool_dif_time = pool_end_time - pool_start_time
 
p_s_time = time.time()
p_list = []
for i in range(100):
p1 = Process(target=func, args=(i,))
p1.start()
p_list.append(p1)
[p.join() for p in p_list]
p_e_time = time.time()
p_dif_time = p_e_time - p_s_time
 
print('進程池的執行時間', pool_dif_time)
print('多進程的執行時間', p_dif_time)
 
 
有一點,map是異步執行的,而且自帶close和join
  通常約定俗成的是進程池中的進程數量爲CPU的數量,工做中要看具體狀況來考量。
 
查看cpu的個數引入os模塊
os.cpu_count()
 
 
同步: 
 
Apply : 同步執行,串行,一個完了一個的執行,效率低, 直接就能拿到返回值
import time
from multiprocessing import Process, Pool
 
 
def fun(i):
time.sleep(0.5)
# print(i)
return i ** 2
 
 
if __name__ == '__main__':
p = Pool(4)
for i in range(10):
res = p.apply(fun, args=(i,)) # 同步執行的方法,他會等待你的任務的返回結果,
print(res)
異步: 
 
    Apply_async() : 異步執行任務,異步的最大量是你進程池中進程的個數
 
import time
from multiprocessing import Process, Pool
 
 
def fun(i):
time.sleep(1)
print(i)
return i ** 2
 
 
if __name__ == '__main__':
p = Pool(4)
res_list = []
for i in range(10):
res = p.apply_async(fun, args=(i,)) # 同步執行的方法,他會等待你的任務的返回結果,
# print('結果:',res.get())
res_list.append(res)
 
p.close() # 不是關閉進程池,而是不容許再有其餘任務來使用進程池
p.join() # 這是感知進程池中任務的方法,進程池中全部的進程隨着主進程的結束而結束了,等待進程池的任務所有執行完
time.sleep(2)
# 循環打印結果
for e_res in res_list:
print('結果:', e_res.get())
 
print('主進程結束')
 
 
 
 
 
# 回調函數
callback
須要回調函數的場景:進程池中任何一個任務一旦處理完了,就當即告知主進程:我好了額,你能夠處理個人結果了。主進程則調用一個函數去處理該結果,該函數即回調函數,這是進程池特有的,普通進程沒有這個機制,可是咱們也能夠經過進程通訊來拿到返回值,進程池的這個回調也是進程通訊的機制完成的。
 
咱們能夠把耗時間(阻塞)的任務放到進程池中,而後指定回調函數(主進程負責執行),這樣主進程在執行回調函數時就省去了I/O的過程,直接拿到的是任務的結果
callback
Close()
Join()
 
from multiprocessing import Pool
 
def func1(n):
這裏能夠打印一下pid
print("func1")
return n + 1
 
 
def func2(nn):
這裏能夠打印一下pid
print("func2")
print(nn)
 
 
if __name__ == '__main__':
這裏能夠打印一下pid
p = Pool(4)
#args裏面的10給了func1,func1的返回值做爲回調函數的參數給了callback對應的函數,不能直接給回調函數直接傳參數,他只能是你任務函數func1的函數的返回值
# for i in range(10,20): #若是是多個進程來執行任務,那麼當全部子進程將結果給了回調函數以後,回調函數又是在主進程上執行的,那麼就會出現打印結果是同步的效果。咱們上面func2裏面註銷的時間模塊打開看看
# p.apply_async(func1,args=(i,),callback=func2)
p.apply_async(func1, args=(10,), callback=func2)
p.close()
p.join()
 
#結果
# 主進程: 11852 #發現回調函數是在主進程中完成的,其實若是是在子進程中完成的,那咱們直接將代碼寫在子進程的任務函數func1裏面就好了,對不對,這也是爲何稱爲回調函數的緣由。
# func1>> 17332
# func1
# func2>> 11852
# func2
# 100
 
回調函數在寫的時候注意一點,回調函數的形參執行有一個,若是你的執行函數有多個返回值,那麼也能夠被回調函數的這一個形參接收,接收的是一個元祖,包含着你執行函數的全部返回值。
 
 
 
 
 
 

線程:python

 

協程:linux

相關文章
相關標籤/搜索