Python-併發編程

一.背景知識python

  顧名思義,進程即正在執行的一個過程。進程是對正在運行程序的一個抽象。linux

  進程的概念起源於操做系統,是操做系統最核心的概念,也是操做系統提供的最古老也是最重要的抽象概念之一。操做系統的其餘全部內容都是圍繞進程的概念展開的。nginx

  這裏關於操做系統的知識就不說了,有興趣的能夠本身再瞭解瞭解web

  PS:即便能夠利用的cpu只有一個(早期的計算機確實如此),也能保證支持(僞)併發的能力。將一個單獨的cpu變成多個虛擬的cpu(多道技術:時間多路複用和空間多路複用+硬件上支持隔離),沒有進程的抽象,現代計算機將不復存在。算法

  必備的理論基礎:shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#一 操做系統的做用:
     1 :隱藏醜陋複雜的硬件接口,提供良好的抽象接口
     2 :管理、調度進程,而且將多個進程對硬件的競爭變得有序
 
#二 多道技術:
     1. 產生背景:針對單核,實現併發
     ps:
     如今的主機通常是多核,那麼每一個核都會利用多道技術
     4 個cpu,運行於cpu1的某個程序遇到io阻塞,會等到io結束再從新調度,會被調度到 4
     cpu中的任意一個,具體由操做系統調度算法決定。
     
     2. 空間上的複用:如內存中同時有多道程序
     3. 時間上的複用:複用一個cpu的時間片
        強調:遇到io切,佔用cpu時間過長也切,核心在於切以前將進程的狀態保存下來,這樣
             才能保證下次切換回來時,能基於上次切走的位置繼續運行

二.什麼是進程windows

  進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。咱們本身在python文件中寫了一些代碼,這叫作程序,運行這個python文件的時候,這叫作進程。安全

  狹義定義:進程是正在運行的程序的實例(an instance of a computer program that is being executed)。
  廣義定義:進程是一個具備必定獨立功能的程序關於某個數據集合的一次運行活動。它是操做系統動態執行的基本單元,在傳統的操做系統中,進程既是基本的分配單元,也是基本的執行單元。
  舉例: 好比py1文件中有個變量a=1,py2文件中有個變量a=2,他們兩個會衝突嗎?不會的,是否是,由於兩個文件運行起來後是兩個進程,操做系統讓他們在內存上隔離開,對吧。
  進程的特性:
1
2
3
4
5
6
動態性:進程的實質是程序在多道程序系統中的一次執行過程,進程是動態產生,動態消亡的。
併發性:任何進程均可以同其餘進程一塊兒併發執行
獨立性:進程是一個能獨立運行的基本單位,同時也是系統分配資源和調度的獨立單位;
異步性:因爲進程間的相互制約,使進程具備執行的間斷性,即進程按各自獨立的、不可預知的速度向前推動
結構特徵:進程由程序、數據和進程控制塊三部分組成。
多個不一樣的進程能夠包含相同的程序:一個程序在不一樣的數據集裏就構成不一樣的進程,能獲得不一樣的結果;可是執行過程當中,程序不能發生改變。

  進程與程序的區別:網絡

1
2
3
4
5
程序是指令和數據的有序集合,其自己沒有任何運行的含義,是一個靜態的概念。
而進程是程序在處理機上的一次執行過程,它是一個動態的概念。
程序能夠做爲一種軟件資料長期存在,而進程是有必定生命期的。
程序是永久的,進程是暫時的。
舉例:就像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裏面能夠設置某個進程的優先級,提升了有限級有可能就會多執行幾個時間片。

四.併發與並行

  經過進程之間的調度,也就是進程之間的切換,咱們用戶感知到的好像是兩個視頻文件同時在播放,或者音樂和遊戲同時在進行,那就讓咱們來看一下什麼叫作併發和並行

不管是並行仍是併發,在用戶看來都是'同時'運行的,無論是進程仍是線程,都只是一個任務而已,真是幹活的是cpu,cpu來作這些任務,而一個cpu同一時刻只能執行一個任務

    併發:是僞並行,即看起來是同時運行。單個cpu+多道技術就能夠實現併發,(並行也屬於併發)

1
2
你是一個cpu,你同時談了三個女友,每個均可以是一個戀愛任務,你被這三個任務共享要玩出併發戀愛的效果,
應該是你先跟女朋友 1 去看電影,看了一會說:很差,我要拉肚子,而後跑去跟第二個女朋友吃飯,吃了一會說:那啥,我去趟洗手間,而後跑去跟女朋友 3 開了個房,而後在你的基友眼裏,你就在和三個女朋友同時在一塊兒玩。

    並行:並行:同時運行,只有具有多個cpu才能實現並行

1
將多個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.同步異步

    所謂同步就是一個任務的完成須要依賴另一個任務時,只有等待被依賴的任務完成後,依賴的任務才能算完成,這是一種可靠的任務序列。要麼成功都成功,失敗都失敗,兩個任務的狀態能夠保持一致。其實就是一個程序結束才執行另一個程序,串行的,不必定兩個程序就有依賴關係。

    所謂異步是不須要等待被依賴的任務完成,只是通知被依賴的任務要完成什麼工做,依賴的任務也當即執行,只要本身完成了整個任務就算完成了。至於被依賴的任務最終是否真正完成,依賴它的任務沒法肯定,因此它是不可靠的任務序列

1
2
3
好比咱們去樓下的老家肉餅吃飯,飯點好了,取餐的時候發生了一些同步異步的事情。
同步:咱們都站在隊裏等着取餐,前面有我的點了一份肉餅,後廚作了好久,可是因爲同步機制,咱們仍是要站在隊裏等着前面那我的的肉餅作好取走,咱們才往前走一步。
異步:咱們點完餐以後,點餐員給了咱們一個取餐號碼,跟你說,你不用在這裏排隊等着,去找個地方坐着玩手機去吧,等飯作好了,我叫你。這種機制(等待別人通知)就是異步等待消息通知。在異步消息處理中,等待消息通知者(在這個例子中等着取餐的你)每每註冊一個回調機制,在所等待的事件被觸發時由觸發機制(點餐員)經過某種機制(喊號,‘ 250 號你的包子好了‘)找到等待該事件的人。

  3.阻塞與非阻塞

   阻塞和非阻塞這兩個概念與程序(線程)等待消息通知(無所謂同步或者異步)時的狀態有關。也就是說阻塞與非阻塞主要是程序(線程)等待消息通知時的狀態角度來講的

1
2
繼續上面的那個例子,不管是排隊仍是使用號碼等待通知,若是在這個等待的過程當中,等待者除了等待消息通知以外不能作其它的事情,那麼該機制就是阻塞的,表如今程序中,也就是該程序一直阻塞在該函數調用處不能繼續往下執行。
相反,有的人喜歡在等待取餐的時候一邊打遊戲一邊等待,這樣的狀態就是非阻塞的,由於他(等待者)沒有阻塞在這個消息通知上,而是一邊作本身的事情一邊等待。阻塞的方法: input 、time.sleep,socket中的recv、accept等等。

  4.同步/異步 與 阻塞和非阻塞

  1. 同步阻塞形式

    效率最低。拿上面的例子來講,就是你專心排隊,什麼別的事都不作。

  1. 異步阻塞形式

    若是在排隊取餐的人採用的是異步的方式去等待消息被觸發(通知),也就是領了一張小紙條,假如在這段時間裏他不能作其它的事情,就在那坐着等着,不能玩遊戲等,那麼很顯然,這我的被阻塞在了這個等待的操做上面;

    異步操做是能夠被阻塞住的,只不過它不是在處理消息時阻塞,而是在等待消息通知時被阻塞。

  1. 同步非阻塞形式

    其實是效率低下的。

    想象一下你一邊打着電話一邊還須要擡頭看到底隊伍排到你了沒有,若是把打電話和觀察排隊的位置當作是程序的兩個操做的話,這個程序須要在這兩種不一樣的行爲之間來回的切換,效率可想而知是低下的。

  1. 異步非阻塞形式

    效率更高,

    由於打電話是你(等待者)的事情,而通知你則是櫃檯(消息觸發機制)的事情,程序沒有在兩種不一樣的操做中來回切換

    好比說,這我的忽然發覺本身煙癮犯了,須要出去抽根菸,因而他告訴點餐員說,排到我這個號碼的時候麻煩到外面通知我一下,那麼他就沒有被阻塞在這個等待的操做上面,天然這個就是異步+非阻塞的方式了。

  不少人會把同步和阻塞混淆,是由於不少時候同步操做會以阻塞的形式表現出來,一樣的,不少人也會把異步和非阻塞混淆,由於異步操做通常都不會在真正的IO操做處被阻塞

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

  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),每一個進程佔用一個進程表項(這些表項也稱爲進程控制塊)

    

    該表存放了進程狀態的重要信息:程序計數器、堆棧指針、內存分配情況、全部打開文件的狀態、賬號和調度信息,以及其餘在進程由運行態轉爲就緒態或阻塞態時,必須保存的信息,從而保證該進程在再次啓動時,就像從未被中斷過同樣。

 

  上面的內容都是進程的一些理論基礎,下面的內容是python中進程的應用實戰

七.multiprocess模塊

  仔細說來,multiprocess不是一個模塊而是python中一個操做、管理進程的包。 之因此叫multi是取自multiple的多功能的意思,在這個包中幾乎包含了和進程有關的全部子模塊。因爲提供的子模塊很是多,爲了方便你們歸類記憶,我將這部分大體分爲四個部分:建立進程部分,進程同步部分,進程池部分,進程之間數據共享。重點強調:進程沒有任何共享狀態,進程修改的數據,改動僅限於該進程內,可是經過一些特殊的方法,能夠實現進程之間數據的共享。

  1.process模塊介紹

     process模塊是一個建立進程的模塊,藉助這個模塊,就能夠完成進程的建立。

1
2
3
4
5
Process([group [, target [, name [, args [, kwargs]]]]]),由該類實例化獲得的對象,表示一個子進程中的任務(還沒有啓動)
 
強調:
1.  須要使用關鍵字的方式來指定參數
2.  args指定的爲傳給target函數的位置參數,是一個元組形式,必須有逗號

    咱們先寫一個程序來看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#當前文件名稱爲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舉例),來看看是不是父子關係。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import  time
import  os
 
#os.getpid()  獲取本身進程的ID號
#os.getppid() 獲取本身進程的父進程的ID號
 
from  multiprocessing  import  Process
 
def  func():
     print ( 'aaaa' )
     time.sleep( 1 )
     print ( '子進程>>' ,os.getpid())
     print ( '該子進程的父進程>>' ,os.getppid())
     print ( 12345 )
 
if  __name__  = =  '__main__' :
     #首先我運行當前這個文件,運行的這個文件的程序,那麼就產生了主進程
 
     =  Process(target = func,)
     p.start()
     print ( '*'  *  10 )
     print ( '父進程>>' ,os.getpid())
     print ( '父進程的父進程>>' ,os.getppid())
 
#加上time和進程號給你們看一看結果:
#********** 首先打印出來了出進程的程序,而後打印的是子進程的,也就是子進程是異步執行的,至關於主進程和子進程同時運行着,若是是同步的話,咱們先執行的是func(),而後再打印主進程最後的10個*號。
#父進程>> 3308
#父進程的父進程>> 5916 #我運行的test.py文件的父進程號,它是pycharm的進程號,看下面的截圖
 
#aaaa
#子進程>> 4536
#該子進程的父進程>> 3308 #是我主進程的ID號,說明主進程爲它的父進程

    

打開windows下的任務管理器,看pycharm的pid進程號,是咱們上面運行的test.py這個文件主進程的父進程號:

    

 

    看一個問題,說明linux和windows兩個不一樣的操做系統建立進程的不一樣機制致使的不一樣結果: 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
2
3
4
5
6
參數介紹:
1  group參數未使用,值始終爲 None
2  target表示調用對象,即子進程要執行的任務
3  args表示調用對象的位置參數元組,args = ( 1 , 2 , 'egon' ,)
4  kwargs表示調用對象的字典,kwargs = { 'name' : 'egon' , 'age' : 18 }
5  name爲子進程的名稱

    給要執行的函數傳參數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def  func(x,y):
     print (x)
     time.sleep( 1 )
     print (y)
 
if  __name__  = =  '__main__' :
 
     =  Process(target = func,args = ( '姑娘' , '來玩啊!' )) #這是func須要接收的參數的傳送方式。
     p.start()
     print ( '父進程執行結束!' )
 
#執行結果:
父進程執行結束!
姑娘
來玩啊!

    Process類中各方法的介紹:

1
2
3
4
5
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感受就像是將子進程和主進程拼接起來同樣,將異步改成同步執行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def  func(x,y):
     print (x)
     time.sleep( 1 )
     print (y)
 
if  __name__  = =  '__main__' :
 
     =  Process(target = func,args = ( '姑娘' , '來玩啊!' ))
     p.start()
     print ( '我這裏是異步的啊!' )   #這裏相對於子進程仍是異步的
     p.join()   #只有在join的地方纔會阻塞住,將子進程和主進程之間的異步改成同步
     print ( '父進程執行結束!' )
 
#打印結果:
我這裏是異步的啊!
姑娘
來玩啊!
父進程執行結束!

    怎麼樣開啓多個進程呢?for循環。而且我有個需求就是說,全部的子進程異步執行,而後全部的子進程所有執行完以後,我再執行主進程,怎麼搞?看代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#下面的註釋按照編號去看,別忘啦!
import  time
import  os
from  multiprocessing  import  Process
 
def  func(x,y):
     print (x)
     # time.sleep(1) #進程切換:若是沒有這個時間間隔,那麼你會發現func執行結果是打印一個x而後一個y,再打印一個x一個y,不會出現打印多個x而後打印y的狀況,由於兩個打印距離太近了並且執行的也很是快,可是若是你這段程序運行慢的話,你就會發現進程之間的切換了。
     print (y)
 
if  __name__  = =  '__main__' :
 
     p_list =  []
     for  in  range ( 10 ):
         =  Process(target = func,args = ( '姑娘%s' % i, '來玩啊!' ))
         p_list.append(p)
         p.start()
 
     [ap.join()  for  ap  in  p_list]  #四、這是解決辦法,前提是咱們的子進程所有都已經去執行了,那麼我在一次給全部正在執行的子進程加上join,那麼主進程就須要等着全部子進程執行結束纔會繼續執行本身的程序了,而且保障了全部子進程是異步執行的。
 
         # p.join() #一、若是加到for循環裏面,那麼全部子進程包括父進程就所有變爲同步了,由於for循環也是主進程的,循環第一次的時候,一個進程去執行了,而後這個進程就join住了,那麼for循環就不會繼續執行了,等着第一個子進程執行結束纔會繼續執行for循環去建立第二個子進程。
         #二、若是我不想這樣的,也就是我想全部的子進程是異步的,而後全部的子進程執行完了再執行主進程
     #p.join() #三、若是這樣寫的話,屢次運行以後,你會發現會出現主進程的程序比一些子進程先執行完,由於咱們p.join()是對最後一個子進程進行了join,也就是說若是這最後一個子進程先於其餘子進程執行完,那麼主進程就會去執行,而此時若是還有一些子進程沒有執行完,而主進程執行
          #完了,那麼就會先打印主進程的內容了,這個cpu調度進程的機制有關係,由於咱們的電腦可能只有4個cpu,個人子進程加上住進程有11個,雖然我for循環是按順序起進程的,可是操做系統必定會按照順序給你執行你的進程嗎,答案是不會的,操做系統會按照本身的算法來分配進
               #程給cpu去執行,這裏也解釋了咱們打印出來的子進程中的內容也是沒有固定順序的緣由,由於打印結果也須要調用cpu,能夠理解成進程在爭搶cpu,若是同窗你想問這是什麼算法,這就要去研究操做系統啦。那咱們的想全部子進程異步執行,而後再執行主進程的這個需求怎麼解決啊
     print ( '不要錢~~~~~~~~~~~~~~~~!' )

    模擬兩個應用場景:一、同時對一個文件進行寫操做  二、同時建立多個文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import  time
import  os
import  re
from  multiprocessing  import  Process
#多進程同時對一個文件進行寫操做
def  func(x,y,i):
     with  open (x, 'a' ,encoding = 'utf-8' ) as f:
         print ( '當前進程%s拿到的文件的光標位置>>%s' % (os.getpid(),f.tell()))
         f.write(y)
 
#多進程同時建立多個文件
# def func(x, y):
#     with open(x, 'w', encoding='utf-8') as f:
#         f.write(y)
 
if  __name__  = =  '__main__' :
 
     p_list =  []
     for  in  range ( 10 ):
         =  Process(target = func,args = ( 'can_do_girl_lists.txt' , '姑娘%s' % i,i))
         # p = Process(target=func,args=('can_do_girl_info%s.txt'%i,'姑娘電話0000%s'%i))
         p_list.append(p)
         p.start()
 
     [ap.join()  for  ap  in  p_list]  #這就是個for循環,只不過用列表生成式的形式寫的
     with  open ( 'can_do_girl_lists.txt' , 'r' ,encoding = 'utf-8' ) as f:
         data  =  f.read()
         all_num  =  re.findall( '\d+' ,data)  #打開文件,統計一下里面有多少個數據,每一個數據都有個數字,因此re匹配一下就好了
         print ( '>>>>>' ,all_num, '.....%s' % ( len (all_num)))
     #print([i in in os.walk(r'你的文件夾路徑')])
     print ( '不要錢~~~~~~~~~~~~~~~~!' )

    Process類中自帶封裝的各屬性的介紹

1
2
3
4
5
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中Process()必須放到# if __name__ == '__main__':下

1
2
3
4
5
6
7
8
9
Since Windows has no fork, the multiprocessing module starts a new Python process  and  imports the calling module.
If Process() gets called upon  import , then this sets off an infinite succession of new processes ( or  until your machine runs out of resources).
This  is  the reason  for  hiding calls to Process() inside
 
if  __name__  = =  "__main__"
since statements inside this  if - statement will  not  get called upon  import .
因爲Windows沒有fork,多處理模塊啓動一個新的Python進程並導入調用模塊。
若是在導入時調用Process(),那麼這將啓動無限繼承的新進程(或直到機器耗盡資源)。
這是隱藏對Process()內部調用的原,使用 if  __name__  = =  「__main __」,這個 if 語句中的語句將不會在導入時被調用。

    進程的建立第二種方法(繼承)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class  MyProcess(Process):  #本身寫一個類,繼承Process類
     #咱們經過init方法能夠傳參數,若是隻寫一個run方法,那麼無法傳參數,由於建立對象的是傳參就是在init方法裏面,面向對象的時候,咱們是否是學過
     def  __init__( self ,person):
         super ().__init__()
         self .person = person
     def  run( self ):
         print (os.getpid())
         print ( self .pid)
         print ( self .pid)
         print ( '%s 正在和女主播聊天'  % self .person)
     # def start(self):
     #     #若是你非要寫一個start方法,能夠這樣寫,而且在run方法先後,能夠寫一些其餘的邏輯
     #     self.run()
if  __name__  = =  '__main__' :
     p1 = MyProcess( 'Jedan' )
     p2 = MyProcess( '太白' )
     p3 = MyProcess( 'alexDSB' )
 
     p1.start()  #start內部會自動調用run方法
     p2.start()
     # p2.run()
     p3.start()
 
 
     p1.join()
     p2.join()
     p3.join()

    進程之間的數據是隔離的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#咱們說進程之間的數據是隔離的,也就是數據不共享,看下面的驗證
from  multiprocessing  import  Process
n = 100  #首先我定義了一個全局變量,在windows系統中應該把全局變量定義在if __name__ == '__main__'之上就能夠了
def  work():
     global  n
     n = 0
     print ( '子進程內: ' ,n)
 
if  __name__  = =  '__main__' :
     p = Process(target = work)
     p.start()
     p.join()  #等待子進程執行完畢,若是數據共享的話,我子進程是否是經過global將n改成0了,可是你看打印結果,主進程在子進程執行結束以後,仍然是n=100,子進程n=0,說明子進程對n的修改沒有在主進程中生效,說明什麼?說明他們之間的數據是隔離的,互相不影響的
     print ( '主進程內: ' ,n)
 
#看結果:
# 子進程內:  0
# 主進程內:  100

    練習:咱們以前學socket的時候,知道tcp協議的socket是不能同時和多個客戶端進行鏈接的,(這裏先不考慮socketserver那個模塊),對不對,那咱們本身經過多進程來實現一下同時和多個客戶端進行鏈接通訊。

     服務端代碼示例:(注意一點:經過這個是不能作qq聊天的,由於qq聊天是qq的客戶端把信息發給另一個qq的客戶端,中間有一個服務端幫你轉發消息,而不是咱們這樣的單純的客戶端和服務端對話,而且子進程開啓以後我們是無法操做的,而且沒有爲子進程input輸入提供控制檯,全部你再在子進程中寫上了input會報錯,EOFError錯誤,這個錯誤的意思就是你的input須要輸入,可是你輸入不了,就會報這個錯誤。而子進程的輸出打印之類的,是pycharm作了優化,將全部子進程中的輸出結果幫你打印出來了,但實質仍是不一樣進程的。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from  socket  import  *
from  multiprocessing  import  Process
 
def  talk(conn,client_addr):
     while  True :
         try :
             msg = conn.recv( 1024 )
             print ( '客戶端消息>>' ,msg)
             if  not  msg: break
             conn.send(msg.upper())
             #在這裏有同窗可能會想,我能不能在這裏寫input來本身輸入內容和客戶端進行對話?朋友,是這樣的,按說是能夠的,可是須要什麼呢?須要你像咱們用pycharm的是同樣下面有一個輸入內容的控制檯,當咱們的子進程去執行的時候,咱們是沒有地方能夠顯示可以讓你輸入內容的控制檯的,因此你沒辦法輸入,就會給你報錯。
         except  Exception:
             break
 
if  __name__  = =  '__main__' #windows下start進程必定要寫到這下面
     server  =  socket(AF_INET, SOCK_STREAM)
     # server.setsockopt(SOL_SOCKET, SO_REUSEADDR,1)  # 若是你將若是你將bind這些代碼寫到if __name__ == '__main__'這行代碼的上面,那麼地址重用必需要有,由於咱們知道windows建立的子進程是對整個當前文件的內容進行的copy,前面說了就像import,若是你開啓了子進程,那麼子進程是會執行bind的,那麼你的主進程bind了這個ip和端口,子進程在進行bind的時候就會報錯。
     server.bind(( '127.0.0.1' 8080 ))
     #有同窗可能還會想,我爲何多個進程就能夠鏈接一個server段的一個ip和端口了呢,我記得當時說tcp的socket的時候,我是不能在你這個ip和端口被鏈接的狀況下再鏈接你的啊,這裏是由於當時咱們就是一個進程,一個進程裏面是隻能一個鏈接的,多進程是能夠多鏈接的,這和進程之間是單獨的內存空間有關係,先這樣記住他,好嗎?
     server.listen( 5 )
     while  True :
         conn,client_addr = server.accept()
         p = Process(target = talk,args = (conn,client_addr))
         p.start()

    客戶端代碼示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
from  socket  import  *
 
client = socket(AF_INET,SOCK_STREAM)
client.connect(( '127.0.0.1' , 8080 ))
 
 
while  True :
     msg = input ( '>>: ' ).strip()
     if  not  msg: continue
 
     client.send(msg.encode( 'utf-8' ))
     msg = client.recv( 1024 )
     print (msg.decode( 'utf-8' ))
相關文章
相關標籤/搜索