1. 程式人生 > >python之併發程式設計—程序

python之併發程式設計—程序

理論知識

作業系統背景知識

回到頂部

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

程序的概念起源於作業系統,是作業系統最核心的概念,也是作業系統提供的最古老也是最重要的抽象概念之一。作業系統的其他所有內容都是圍繞程序的概念展開的。

所以想要真正瞭解程序,必須事先了解作業系統,點選進入    

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

必備的理論基礎:

複製程式碼
#一 作業系統的作用:
    1:隱藏醜陋複雜的硬體介面,提供良好的抽象介面
    2:管理、排程程序,並且將多個程序對硬體的競爭變得有序

#二 多道技術:
    1.產生背景:針對單核,實現併發
    ps:
    現在的主機一般是多核,那麼每個核都會利用多道技術
    有4個cpu,運行於cpu1的某個程式遇到io阻塞,會等到io結束再重新排程,會被排程到4個
    cpu中的任意一個,具體由作業系統排程演算法決定。
    
    2.空間上的複用:如記憶體中同時有多道程式
    3.時間上的複用:複用一個cpu的時間片
       強調:遇到io切,佔用cpu時間過長也切,核心在於切之前將程序的狀態儲存下來,這樣
            才能保證下次切換回來時,能基於上次切走的位置繼續執行
複製程式碼 回到頂部

什麼是程序

程序(Process)是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。在早期面向程序設計的計算機結構中,程序是程式的基本執行實體;在當代面向執行緒設計的計算機結構中,程序是執行緒的容器。程式是指令、資料及其組織形式的描述,程序是程式的實體。

狹義定義:程序是正在執行的程式的例項(an instance of a computer program that is being executed)。 廣義定義:程序是一個具有一定獨立功能的程式關於某個資料集合的一次執行活動。它是
作業系統
動態執行的 基本單元,在傳統的 作業系統中,程序既是基本的 分配單元,也是基本的執行單元。
第一,程序是一個實體。每一個程序都有它自己的地址空間,一般情況下,包括文字區域(text region)、資料區域(data region)和堆疊(stack region)。文字區域儲存處理器執行的程式碼;資料區域儲存變數和程序執行期間使用的動態分配的記憶體;堆疊區域儲存著活動過程呼叫的指令和本地變數。
第二,程序是一個“執行中的程式”。程式是一個沒有生命的實體,只有處理器賦予程式生命時(作業系統執行之),它才能成為一個活動的實體,我們稱其為程序。[3] 
程序是作業系統中最基本、重要的概念。是多道程式系統出現後,為了刻畫系統內部出現的動態情況,描述系統內部各道程式的活動規律引進的一個概念,所有多道程式設計作業系統都建立在程序的基礎上。
程序的概念
從理論角度看,是對正在執行的程式過程的抽象;
從實現角度看,是一種資料結構,目的在於清晰地刻畫動態系統的內在規律,有效管理和排程進入計算機系統主儲存器執行的程式。
作業系統引入程序的概念的原因
動態性:程序的實質是程式在多道程式系統中的一次執行過程,程序是動態產生,動態消亡的。
併發性:任何程序都可以同其他程序一起併發執行
獨立性:程序是一個能獨立執行的基本單位,同時也是系統分配資源和排程的獨立單位;
非同步性:由於程序間的相互制約,使程序具有執行的間斷性,即程序按各自獨立的、不可預知的速度向前推進
結構特徵:程序由程式、資料和程序控制塊三部分組成。
多個不同的程序可以包含相同的程式:一個程式在不同的資料集裡就構成不同的程序,能得到不同的結果;但是執行過程中,程式不能發生改變。
程序的特徵
程式是指令和資料的有序集合,其本身沒有任何執行的含義,是一個靜態的概念。
而程序是程式在處理機上的一次執行過程,它是一個動態的概念。
程式可以作為一種軟體資料長期存在,而程序是有一定生命期的。
程式是永久的,程序是暫時的。
程序與程式中的區別

注意:同一個程式執行兩次,就會在作業系統中出現兩個程序,所以我們可以同時執行一個軟體,分別做不同的事情也不會混亂。

回到頂部

程序排程

要想多個程序交替執行,作業系統必須對這些程序進行排程,這個排程也不是隨即進行的,而是需要遵循一定的法則,由此就有了程序的排程演算法。

先來先服務(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佇列的末尾,把處理機分配給新到的高優先權程序。
多級反饋佇列 回到頂部

程序的並行與併發

並行並行是指兩者同時執行,比如賽跑,兩個人都在不停的往前跑;(資源夠用,比如三個執行緒,四核的CPU )

併發併發是指資源有限的情況下,兩者交替輪流使用資源,比如一段路(單核CPU資源)同時只能過一個人,A走一段後,讓給B,B用完繼續給A ,交替使用,目的是提高效率。

區別:

並行是從微觀上,也就是在一個精確的時間片刻,有不同的程式在執行,這就要求必須有多個處理器。
併發是從巨集觀上,在一個時間段上可以看出是同時執行的,比如一個伺服器同時處理多個session。

回到頂部

同步非同步阻塞非阻塞

狀態介紹

  在瞭解其他概念之前,我們首先要了解程序的幾個狀態。在程式執行的過程中,由於被作業系統的排程演算法控制,程式會進入幾個狀態:就緒,執行和阻塞。

  (1)就緒(Ready)狀態

  當程序已分配到除CPU以外的所有必要的資源,只要獲得處理機便可立即執行,這時的程序狀態稱為就緒狀態。

  (2)執行/執行(Running)狀態當程序已獲得處理機,其程式正在處理機上執行,此時的程序狀態稱為執行狀態。

  (3)阻塞(Blocked)狀態正在執行的程序,由於等待某個事件發生而無法執行時,便放棄處理機而處於阻塞狀態。引起程序阻塞的事件可有多種,例如,等待I/O完成、申請緩衝區不能滿足、等待信件(訊號)等。

      

同步和非同步

      所謂同步就是一個任務的完成需要依賴另外一個任務時,只有等待被依賴的任務完成後,依賴的任務才能算完成,這是一種可靠的任務序列。要麼成功都成功,失敗都失敗,兩個任務的狀態可以保持一致。

  所謂非同步是不需要等待被依賴的任務完成,只是通知被依賴的任務要完成什麼工作,依賴的任務也立即執行,只要自己完成了整個任務就算完成了。至於被依賴的任務最終是否真正完成,依賴它的任務無法確定,所以它是不可靠的任務序列

比如我去銀行辦理業務,可能會有兩種方式:
第一種 :選擇排隊等候;
第二種 :選擇取一個小紙條上面有我的號碼,等到排到我這一號時由櫃檯的人通知我輪到我去辦理業務了;

第一種:前者(排隊等候)就是同步等待訊息通知,也就是我要一直在等待銀行辦理業務情況;

第二種:後者(等待別人通知)就是非同步等待訊息通知。在非同步訊息處理中,等待訊息通知者(在這個例子中就是等待辦理業務的人)往往註冊一個回撥機制,在所等待的事件被觸發時由觸發機制(在這裡是櫃檯的人)通過某種機制(在這裡是寫在小紙條上的號碼,喊號)找到等待該事件的人。
例子

阻塞與非阻塞

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

繼續上面的那個例子,不論是排隊還是使用號碼等待通知,如果在這個等待的過程中,等待者除了等待訊息通知之外不能做其它的事情,那麼該機制就是阻塞的,表現在程式中,也就是該程式一直阻塞在該函式呼叫處不能繼續往下執行。
相反,有的人喜歡在銀行辦理這些業務的時候一邊打打電話發發簡訊一邊等待,這樣的狀態就是非阻塞的,因為他(等待者)沒有阻塞在這個訊息通知上,而是一邊做自己的事情一邊等待。

注意:同步非阻塞形式實際上是效率低下的,想象一下你一邊打著電話一邊還需要擡頭看到底隊伍排到你了沒有。如果把打電話和觀察排隊的位置看成是程式的兩個操作的話,這個程式需要在這兩種不同的行為之間來回的切換,效率可想而知是低下的;而非同步非阻塞形式卻沒有這樣的問題,因為打電話是你(等待者)的事情,而通知你則是櫃檯(訊息觸發機制)的事情,程式沒有在兩種不同的操作中來回切換。
例子

同步/非同步與阻塞/非阻塞

  1. 同步阻塞形式

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

  1. 非同步阻塞形式

  如果在銀行等待辦理業務的人採用的是非同步的方式去等待訊息被觸發(通知),也就是領了一張小紙條,假如在這段時間裡他不能離開銀行做其它的事情,那麼很顯然,這個人被阻塞在了這個等待的操作上面;

  非同步操作是可以被阻塞住的,只不過它不是在處理訊息時阻塞,而是在等待訊息通知時被阻塞。

  1. 同步非阻塞形式

  實際上是效率低下的。

  想象一下你一邊打著電話一邊還需要擡頭看到底隊伍排到你了沒有,如果把打電話和觀察排隊的位置看成是程式的兩個操作的話,這個程式需要在這兩種不同的行為之間來回的切換,效率可想而知是低下的。

  1. 非同步非阻塞形式

  效率更高,

  因為打電話是你(等待者)的事情,而通知你則是櫃檯(訊息觸發機制)的事情,程式沒有在兩種不同的操作中來回切換

  比如說,這個人突然發覺自己煙癮犯了,需要出去抽根菸,於是他告訴大堂經理說,排到我這個號碼的時候麻煩到外面通知我一下,那麼他就沒有被阻塞在這個等待的操作上面,自然這個就是非同步+非阻塞的方式了。

  

很多人會把同步和阻塞混淆,是因為很多時候同步操作會以阻塞的形式表現出來,同樣的,很多人也會把非同步和非阻塞混淆,因為非同步操作一般都不會在真正的IO操作處被阻塞

回到頂部

程序的建立與結束

程序的建立

  但凡是硬體,都需要有作業系統去管理,只要有作業系統,就有程序的概念,就需要有建立程序的方式,一些作業系統只為一個應用程式設計,比如微波爐中的控制器,一旦啟動微波爐,所有的程序都已經存在。

  而對於通用系統(跑很多應用程式),需要有系統執行過程中建立或撤銷程序的能力,主要分為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系統來說,從一開始父程序與子程序的地址空間就是不同的。
建立程序

程序的結束

  1. 正常退出(自願,如使用者點選互動式頁面的叉號,或程式執行完畢呼叫發起系統呼叫正常退出,在linux中用exit,在windows中用ExitProcess)

  2. 出錯退出(自願,python a.py中a.py不存在)

  3. 嚴重錯誤(非自願,執行非法指令,如引用不存在的記憶體,1/0等,可以捕捉異常,try...except...)

  4. 被其他程序殺死(非自願,如kill -9)

回到頂部

在python程式中的程序操作

  之前我們已經瞭解了很多程序相關的理論知識,瞭解程序是什麼應該不再困難了,剛剛我們已經瞭解了,執行中的程式就是一個程序。所有的程序都是通過它的父程序來建立的。因此,執行起來的python程式也是一個程序,那麼我們也可以在程式中再建立程序。多個程序可以實現併發效果,也就是說,當我們的程式中存在多個程序的時候,在某些時候,就會讓程式的執行速度變快。以我們之前所學的知識,並不能實現建立程序這個功能,所以我們就需要藉助python中強大的模組。

回到頂部

multiprocess模組

      仔細說來,multiprocess不是一個模組而是python中一個操作、管理程序的包。 之所以叫multi是取自multiple的多功能的意思,在這個包中幾乎包含了和程序有關的所有子模組。由於提供的子模組非常多,為了方便大家歸類記憶,我將這部分大致分為四個部分:建立程序部分,程序同步部分,程序池部分,程序之間資料共享。

回到頂部

multiprocess.process模組

process模組介紹

process模組是一個建立程序的模組,藉助這個模組,就可以完成程序的建立。

複製程式碼
Process([group [, target [, name [, args [, kwargs]]]]]),由該類例項化得到的物件,表示一個子程序中的任務(尚未啟動)

強調:
1. 需要使用關鍵字的方式來指定引數
2. args指定的為傳給target函式的位置引數,是一個元組形式,必須有逗號

引數介紹:
1 group引數未使用,值始終為None
2 target表示呼叫物件,即子程序要執行的任務
3 args表示呼叫物件的位置引數元組,args=(1,2,'egon',)
4 kwargs表示呼叫物件的字典,kwargs={'name':'egon','age':18}
5 name為子程序的名稱
複製程式碼
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開啟的程序  
方法介紹
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字元的字串。這個鍵的用途是為涉及網路連線的底層程序間通訊提供安全性,這類連線只有在具有相同的身份驗證鍵時才能成功(瞭解即可)
屬性介紹
在Windows作業系統中由於沒有fork(linux作業系統中建立程序的機制),在建立子程序的時候會自動 import 啟動它的這個檔案,而在 import 的時候又執行了整個檔案。因此如果將process()直接寫在檔案中就會無限遞迴建立子程序報錯。所以必須把建立子程序的部分使用if __name__ ==‘__main__’ 判斷保護起來,import 的時候  ,就不會遞迴運行了。
在windows中使用process模組的注意事項

 

使用process模組建立程序

在一個python程序中開啟子程序,start方法和併發效果。

import time
from multiprocessing import Process

def f(name):
    print('hello', name)
    print('我是子程序')

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    time.sleep(1)
    print('執行主程序的內容了')
    
在python中啟動的第一個子程序
import time
from multiprocessing import Process

def f(name):
    print('hello', name)
    time.sleep(1)
    print('我是子程序')


if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    #p.join()
    print('我是父程序')
join方法
import os
from multiprocessing import Process

def f(x):
    print('子程序id :',os.getpid(),'父程序id :',os.getppid())
    return x*x

if __name__ == '__main__':
    print('主程序id :', os.getpid())
    p_lst = []
    for i in range(5):
        p = Process(target=f, args=(i,))
        p.start()
檢視主程序和子程序的程序號

 

進階,多個程序同時執行(注意,子程序的執行順序不是根據啟動順序決定的)

import time
from multiprocessing import Process


def f(name):
    print('hello', name)
    time.sleep(1)


if __name__ == '__main__':
    p_lst = []
    for i in range(5):
        p = Process(target=f, args=('bob',))
        p.start()
        p_lst.append(p)
多個程序同時執行
import time
from multiprocessing import Process


def f(name):
    print('hello', name)
    time.sleep(1)


if __name__ == '__main__':
    p_lst = []
    for i in range(5):
        p = Process(target=f, args=('bob',))
        p.start()
        p_lst.append(p)
        p.join()
    # [p.join() for p in p_lst]
    print('父程序在執行')
多個程序同時執行,再談join方法(1)
import time
from multiprocessing import Process

def f(name):
    print('hello', name)
    time.sleep(1)

if __name__ == '__main__':
    p_lst = []
    for i in range(5):
        p = Process(target=f, args=('bob',))
        p.start()
        p_lst.append(p)
    # [p.join() for p in p_lst]
    print('父程序在執行')
多個程序同時執行,再談join方法(2)

 

除了上面這些開啟程序的方法,還有一種以繼承Process類的形式開啟程序的方式

import os
from multiprocessing import Process


class MyProcess(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        print(os.getpid())
        print('%s 正在和女主播聊天' %self.name)

p1=MyProcess('wupeiqi')
p2=MyProcess('yuanhao')
p3=MyProcess('nezha')

p1.start() #start會自動呼叫run
p2.start()
# p2.run()
p3.start()


p1.join()
p2.join()
p3.join()

print('主執行緒')
通過繼承Process類開啟程序

 

程序之間的資料隔離問題

from multiprocessing import Process

def work():
    global n
    n=0
    print('子程序內: ',n)


if __name__ == '__main__':
    n = 100
    p=Process(target=work)
    p.start()
    print('主程序內: ',n)
程序之間的資料隔離問題

守護程序

會隨著主程序的結束而結束。

主程序建立守護程序

  其一:守護程序會在主程序程式碼執行結束後就終止

  其二:守護程序內無法再開啟子程序,否則丟擲異常:AssertionError: daemonic processes are not allowed to have children

注意:程序之間是互相獨立的,主程序程式碼執行結束,守護程序隨即終止

import os
import time
from multiprocessing import Process

class Myprocess(Process):
    def __init__(self,person):
        super().__init__()
        self.person = person
    def run(self):
        print(os.getpid(),self.name)
        print('%s正在和女主播聊天' %self.person)


p=Myprocess('哪吒')
p.daemon=True #一定要在p.start()前設定,設定p為守護程序,禁止p建立子程序,並且父程序程式碼執行結束,p即終止執行
p.start()
time.sleep(10) # 在sleep時檢視程序id對應的程序ps -ef|grep id
print('')
守護程序的啟動
from multiprocessing import Process

def foo():
    print(123)
    time.sleep(1)
    print("end123")

def bar():
    print(456)
    time.sleep(3)
    print("end456")


p1=Process(target=foo)
p2=Process(target=bar)

p1.daemon=True
p1.start()
p2.start()
time.sleep(0.1)
print("main-------")#列印該行則主程序程式碼結束,則守護程序p1應該被終止.#可能會有p1任務執行的列印資訊123,因為主程序列印main----時,p1也執行了,但是隨即被終止.
主程序程式碼執行結束守護程序立即結束

socket聊天併發例項

from socket import *
from multiprocessing import Process

server=socket(AF_INET,SOCK_STREAM)
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
server.bind(('127.0.0.1',8080))
server.listen(5)

def talk(conn,client_addr):
    while True:
        try:
            msg=conn.recv(1024)
            if not msg:break
            conn.send(msg.upper())
        except Exception:
            break

if __name__ == '__main__': #windows下start程序一定要寫到這下面
    while True:
        conn,client_addr=server.accept()
        p=Process(target=talk,args=(conn,client_addr))
        p.start()
使用多程序實現socket聊天併發-server
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'))
client端

多程序中的其他方法

from multiprocessing import Process
import time
import random

class Myprocess(Process):
    def __init__(self,person):
        self.name=person
        super().__init__()

    def run(self):
        print('%s正在和網紅臉聊天' %self.name)
        time.sleep(random.randrange(1,5))
        print('%s還在和網紅臉聊天' %self.name)


p1=Myprocess('哪吒')
p1.start()

p1.terminate()#關閉程序,不會立即關閉,所以is_alive立刻檢視的結果可能還是存活
print(p1.is_alive()) #結果為True

print('開始')
print(p1.is_alive()) #結果為False
程序物件的其他方法:terminate,is_alive
 1 class Myprocess(Process):
 2     def __init__(self,person):
 3         self.name=person   # name屬性是Process中的屬性,標示程序的名字
 4         super().__init__() # 執行父類的初始化方法會覆蓋name屬性
 5         #self.name = person # 在這裡設定就可以修改程序名字了
 6         #self.person = person #如果不想覆蓋程序名,就修改屬性名稱就可以了
 7     def run(self):
 8         print('%s正在和網紅臉聊天' %self.name)
 9         # print('%s正在和網紅臉聊天' %self.person)
10         time.sleep(random.randrange(1,5))
11         print('%s正在和網紅臉聊天' %self.name)
12         # print('%s正在和網紅臉聊天' %self.person)
13 
14 
15 p1=Myprocess('哪吒')
16 p1.start()
17 print(p1.pid)    #可以檢視子程序的程序id
程序物件的其他屬性:pid和name 回到頂部

程序同步(multiprocess.Lock、multiprocess.Semaphore、multiprocess.Event)

鎖 —— multiprocess.Lock

      通過剛剛的學習,我們千方百計實現了程式的非同步,讓多個任務可以同時在幾個程序中併發處理,他們之間的執行沒有順序,一旦開啟也不受我們控制。儘管併發程式設計讓我們能更加充分的利用IO資源,但是也給我們帶來了新的問題。

  當多個程序使用同一份資料資源的時候,就會引發資料安全或順序混亂問題。

import os
import time
import random
from multiprocessing import Process

def work(n):
    print('%s: %s is running' %(n,os.getpid()))
    time.sleep(random.random())
    print('%s:%s is done' %(n,os.getpid()))

if __name__ == '__main__':
    for i in range(3):
        p=Process(target=work,args=(i,))
        p.start()
多程序搶佔輸出資源
# 由併發變成了序列,犧牲了執行效率,但避免了競爭
import os
import time
import random
from multiprocessing import Process,Lock

def work(lock,n):
    lock.acquire()
    print('%s: %s is running' % (n, os.getpid()))
    time.sleep(random.random())
    print('%s: %s is done' % (n, os.getpid()))
    lock.release()
if __name__ == '__main__':
    lock=Lock()
    for i in range(3):
        p=Process(target=work,args=(lock,i))
        p.start()
使用鎖維護執行順序

  上面這種情況雖然使用加鎖的形式實現了順序的執行,但是程式又重新變成串行了,這樣確實會浪費了時間,卻保證了資料的安全。

  接下來,我們以模擬搶票為例,來看看資料安全的重要性。