python之路——進程

1、什麼是進程

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

  狹義定義:進程是正在運行的程序的實例(an instance of a computer program that is being executed)。
  廣義定義:進程是一個具備必定獨立功能的程序關於某個數據集合的一次運行活動。它是操做系統動態執行的基本單元,在傳統的操做系統中,進程既是基本的分配單元,也是基本的執行單元。

一、進程的概念:

  第一,進程是一個實體。每個進程都有它本身的地址空間,通常狀況下,包括文本區域(text region)、數據區域(data region)和堆棧(stack region)。文本區域存儲處理器執行的代碼;  數據區域存儲變量和進程執行期間使用的動態分配的內存;堆棧區域存儲着活動過程調用的指令和本地變量。linux

  第二,進程是一個「執行中的程序」。程序是一個沒有生命的實體,只有處理器賦予程序生命時(操做系統執行之),它才能成爲一個活動的實體,咱們稱其爲進程。進程是操做系統中最基本、重要的概念。是多道程序系統出現後,爲了刻畫系統內部出現的動態狀況,描述系統內部各道程序的活動規律引進的一個概念,全部多道程序設計操做系統都創建在進程的基礎上。nginx

二、操做系統引入進程概念的緣由:

  從理論角度看,是對正在運行的程序過程的抽象;web

  從實現角度看,是一種數據結構,目的在於清晰地刻畫動態系統的內在規律,有效管理和調度進入計算機系統主存儲器運行的程序。算法

三、進程的特徵:

  動態性:進程的實質是程序在多道程序系統中的一次執行過程,進程是動態產生,動態消亡的。shell

  併發性:任何進程均可以同其餘進程一塊兒併發執行 獨立性:進程是一個能獨立運行的基本單位,同時也是系統分配資源和調度的獨立單位;數據庫

  異步性:因爲進程間的相互制約,使進程具備執行的間斷性,即進程按各自獨立的、不可預知的速度向前推動編程

  結構特徵:進程由程序、數據和進程控制塊三部分組成。 多個不一樣的進程能夠包含相同的程序:一個程序在不一樣的數據集裏就構成不一樣的進程,能獲得不一樣的結果;json

  可是執行過程當中,程序不能發生改變。windows

四、進程與程序的區別:

  程序是指令和數據的有序集合,其自己沒有任何運行的含義,是一個靜態的概念。而進程是程序在處理機上的一次執行過程,它是一個動態的概念。

  程序能夠做爲一種軟件資料長期存在,而進程是有必定生命期的。

  程序是永久的,進程是暫時的。

注意:同一個程序執行兩次,就會在操做系統中出現兩個進程,因此咱們能夠同時運行一個軟件,分別作不一樣的事情也不會混亂。

2、進程調度

  要想多個進程交替運行,操做系統必須對這些進程進行調度,這個調度也不是隨即進行的,而是須要遵循必定的法則,由此就有了進程的調度算法。

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

3、進程的併發與並行

並行 : 並行是指二者同時執行,好比賽跑,兩我的都在不停的往前跑;(資源夠用,好比三個線程,四核的CPU )

併發 : 併發是指資源有限的狀況下,二者交替輪流使用資源,好比一段路(單核CPU資源)同時只能過一我的,A走一段後,讓給B,B用完繼續給A ,交替使用,目的是提升效率。

區別:

並行是從微觀上,也就是在一個精確的時間片刻,有不一樣的程序在執行,這就要求必須有多個處理器。
併發是從宏觀上,在一個時間段上能夠看出是同時執行的,好比一個服務器同時處理多個session。

 4、同步異步阻塞非阻塞

同步和異步:

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

  所謂異步是不須要等待被依賴的任務完成,只是通知被依賴的任務要完成什麼工做,依賴的任務也當即執行,只要本身完成了整個任務就算完成了。至於被依賴的任務最終是否真正完成,依賴它的任務沒法肯定, 因此它是不可靠的任務序列
好比我去銀行辦理業務,可能會有兩種方式:
第一種 :選擇排隊等候;
第二種 :選擇取一個小紙條上面有個人號碼,等到排到我這一號時由櫃檯的人通知我輪到我去辦理業務了;

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

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

阻塞與非阻塞:

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

繼續上面的那個例子,不管是排隊仍是使用號碼等待通知,若是在這個等待的過程當中,等待者除了等待消息通知以外不能作其它的事情,那麼該機制就是阻塞的,表如今程序中,也就是該程序一直阻塞在該函數調用處不能繼續往下執行。
相反,有的人喜歡在銀行辦理這些業務的時候一邊打打電話發發短信一邊等待,這樣的狀態就是非阻塞的,由於他(等待者)沒有阻塞在這個消息通知上,而是一邊作本身的事情一邊等待。

注意:同步非阻塞形式其實是效率低下的,想象一下你一邊打着電話一邊還須要擡頭看到底隊伍排到你了沒有。若是把打電話和觀察排隊的位置當作是程序的兩個操做的話,這個程序須要在這兩種不一樣的行爲之間來回的切換,效率可想而知是低下的;而異步非阻塞形式卻沒有這樣的問題,由於打電話是你(等待者)的事情,而通知你則是櫃檯(消息觸發機制)的事情,程序沒有在兩種不一樣的操做中來回切換。
舉例說明

同步/異步與阻塞/非阻塞:

  1. 同步阻塞形式

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

  1. 異步阻塞形式

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

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

  1. 同步非阻塞形式

  其實是效率低下的。

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

  1. 異步非阻塞形式

  效率更高,

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

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

  

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

 5、進程的建立與結束

進程的建立:

  但凡是硬件,都須要有操做系統去管理,只要有操做系統,就有進程的概念,就須要有建立進程的方式,一些操做系統只爲一個應用程序設計,好比微波爐中的控制器,一旦啓動微波爐,全部的進程都已經存在。

  而對於通用系統(跑不少應用程序),須要有系統運行過程當中建立或撤銷進程的能力,主要分爲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)

6、在python程序中的進程操做

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

一、multiprocess模塊

  仔細說來,multiprocess不是一個模塊而是python中一個操做、管理進程的包。 之因此叫multi是取自multiple的多功能的意思,在這個包中幾乎包含了和進程有關的全部子模塊。因爲提供的子模塊很是多,爲了方便你們歸類記憶,我將這部分大體分爲四個部分:建立進程部分,進程同步部分,進程池部分,進程之間數據共享

   multiprocess.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爲子進程的名稱
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開啓的進程
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字符的字符串。這個鍵的用途是爲涉及網絡鏈接的底層進程間通訊提供安全性,這類鏈接只有在具備相同的身份驗證鍵時才能成功(瞭解便可)
Process屬性介紹
在Windows操做系統中因爲沒有fork(linux操做系統中建立進程的機制),在建立子進程的時候會自動 import 啓動它的這個文件,而在 import 的時候又執行了整個文件。所以若是將process()直接寫在文件中就會無限遞歸建立子進程報錯。因此必須把建立子進程的部分使用if __name__ ==‘__main__’ 判斷保護起來,import 的時候  ,就不會遞歸運行了。
windows系統使用Process注意事項

    使用process模塊建立進程:

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

def func():
    print('個人pid:%s,父進程的pid:%s'%(os.getpid(),os.getppid()))

if __name__ =='__main__':
    print(os.getpid(),os.getppid())
    p = Process(target=func)
    p.start()
Process建立第一個進程
import os
from multiprocessing import Process

def func():
    print('個人pid:%s,父進程的pid:%s'%(os.getpid(),os.getppid()))

if __name__ =='__main__':

    p = Process(target=func)
    p.start()
    p.join()
    print('個人pid:%s,父進程的pid:%s'%(os.getpid(), os.getppid()))
join方法和查看進程號

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

建立多個進程
from multiprocessing import Process
import time

def func(num):
    print('%s<>hello'%num)
    time.sleep(0.5)


if __name__ == '__main__':
    p_list = []
    for i in range(10):
        p = Process(target=func,args=(i,))
        p.start()
        p_list.append(p)
        p.join()
    print('主進程')
多進程之join方法一
from multiprocessing import Process
import time

def func(num):
    print('%s<>hello'%num)
    time.sleep(0.5)


if __name__ == '__main__':
    p_list = []
    for i in range(10):
        p = Process(target=func,args=(i,))
        p.start()
        p_list.append(p)
    [p.join() for p in p_list]
    print('主進程')
多進程之join方法二

  很明顯方法二的執行效率更高,符合併發

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

from multiprocessing import Process


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


    def run(self):
        print('in 子進程')
        print('hello:%s' % self.name)


if __name__ == '__main__':
    p = Myprocess('wangjifei')
    p.start()
    p.join()
    print('in 主進程')
面向對象建立子進程
import os
from multiprocessing import Process
import time

n = 1
def func():
    global n
    n = 100
    print('個人pid:%s,父進程的pid:%s'%(os.getpid(),os.getppid()))

if __name__ =='__main__':
    print(os.getpid(),os.getppid())
    p = Process(target=func)
    p.start()
    time.sleep(1)
    print(n)
進程之間的數據隔離

  經過建立子進程實現socket多人聊天功能

import socket
from multiprocessing import Process


def chat(conn):
    conn.send(b'hello')
    print(conn.recv(1024).decode('utf-8'))
    conn.close()
    sk.close()


if __name__ == '__main__':
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8000))
    sk.listen()
    while True:
        conn, addr = sk.accept()
        p = Process(target=chat, args=(conn,))
        p.start()
server
import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 8000))

print(sk.recv(1024))
msg = input('>>>:')
sk.send(msg.encode('utf-8'))

sk.close()
cilent

   守護進程:

  會隨着主進程的結束而結束。

  主進程建立守護進程

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

    其二:守護進程內沒法再開啓子進程,不然拋出異常:AssertionError: daemonic processes are not allowed to have children

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

import time
from multiprocessing import Process

def func1():
    while True:
        print('is alive')
        time.sleep(0.5)

if __name__ == "__main__":
    p = Process(target=func1)
    p.daemon = True  #守護進程參數,這就成爲了守護進程
    p.start()
    time.sleep(3)
    print('主進程')
守護進程的啓動
import time
from multiprocessing import Process


def func1():
    while True:
        print('is alive')
        time.sleep(0.5)


def func2():
    for i in range(10):
        print('第%s秒' % i)
        time.sleep(1)


if __name__ == "__main__":
    Process(target=func2).start()
    p = Process(target=func1)
    p.daemon = True  # 守護進程參數,這就成爲了守護進程
    p.start()
    time.sleep(3)
    print('主進程')
主程序結束,守護進程結束
import time
from multiprocessing import Process


def func1():
    while True:
        print('is alive')
        time.sleep(0.5)



if __name__ == "__main__":
    p = Process(target=func1)
    p.daemon = True
    print(p.is_alive()) #守護進程參數,這就成爲了守護進程
    p.start()
    time.sleep(1)
    print(p.is_alive()) # 判斷進程是否存活
    p.terminate() # 殺死進程
    time.sleep(3)
    print('主進程')
    print(p.is_alive()) # 判斷進程是否存活
進程對象的其餘方法:terminate,is_alive
from multiprocessing import Process
import time
import random

class Myprocess(Process):
    def __init__(self,person):
        self.name=person   # name屬性是Process中的屬性,標示進程的名字
        super().__init__() # 執行父類的初始化方法會覆蓋name屬性
        # self.name = person # 在這裏設置就能夠修改進程名字了
        self.person = person #若是不想覆蓋進程名,就修改屬性名稱就能夠了
    def run(self):
        print('%s正在和網紅臉聊天' %self.name)
        # print('%s正在和網紅臉聊天' %self.person)
        time.sleep(random.randrange(1,5))
        print('%s正在和網紅臉聊天' %self.name)
        # print('%s正在和網紅臉聊天' %self.person)

if __name__ == '__main__':

    p1=Myprocess('哪吒')
    p1.start()
    print(p1.pid)    #能夠查看子進程的進程id
進程對象的其餘屬性:pid和name

7、進程同步(multiprocess.Lock、multiprocess.Semaphore、multiprocess.Event)

  鎖 —— multiprocess.Lock

  經過剛剛的學習,咱們想方設法實現了程序的異步,讓多個任務能夠同時在幾個進程中併發處理,他們之間的運行沒有順序,一旦開啓也不受咱們控制。

  儘管併發編程讓咱們能更加充分的利用IO資源,可是也給咱們帶來了新的問題。

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

from multiprocessing import Lock
lock = Lock()
lock.acquire()  # 想拿鑰匙
print(1)
lock.release()  # 還鑰匙
lock.acquire()  # 想拿鑰匙
print('拿到鑰匙了')
lock.release()  # 還鑰匙
簡單鎖的啓動
import json
import time
from multiprocessing import Process,Lock
def search(i):
    with open('db') as f:ticket_dic = json.load(f)
    print('%s正在查票,剩餘票數 :%s'%(i,ticket_dic['count']))

def buy(i):
    with open('db') as f:ticket_dic = json.load(f)
    time.sleep(0.2)                  # 模擬請求數據庫的網絡延時
    if ticket_dic['count'] > 0:
        ticket_dic['count'] -= 1
        print('%s買到票了'%i)
        time.sleep(0.2)              # 模擬往數據庫寫消息的網絡延遲
        with open('db','w') as f:json.dump(ticket_dic,f)

def get_ticket(i,lock):
    search(i)              # 查詢餘票,能夠多個用戶同時查詢
    with lock : buy(i)     # 買票的操做涉及到數據修改,須要加鎖來保證數據的安全

# def get_ticket(i,lock):
#     search(i)          # 查詢餘票,能夠多個用戶同時查詢
#     lock.acquire()
#     buy(i)             # 買票的操做涉及到數據修改,須要加鎖來保證數據的安全
#     lock.release()

if __name__ == '__main__':
    lock = Lock()
    for i in range(10):
        p = Process(target=get_ticket,args = (i,lock))
        p.start()
經過鎖實現查票功能,只是缺乏bd文件,代碼執行會報錯

鎖的做用: 維護數據的安全
       下降了程序的效率
         全部的效率都是創建在數據安全的角度上的
          但凡涉及到併發編程都要考慮數據的安全性
            咱們須要在併發部分對數據修改的操做格外當心,若是會涉及到數據的不安全,就須要進行加鎖控制

#加鎖能夠保證多個進程修改同一塊數據時,同一時間只能有一個任務能夠進行修改,即串行的修改,沒錯,速度是慢了,但犧牲了速度卻保證了數據安全。
雖然能夠用文件共享數據實現進程間通訊,但問題是:
1.效率低(共享數據基於文件,而文件是硬盤上的數據)
2.須要本身加鎖處理

#所以咱們最好找尋一種解決方案可以兼顧:一、效率高(多個進程共享一塊內存的數據)二、幫咱們處理好鎖問題。這就是mutiprocessing模塊爲咱們提供的基於消息的IPC通訊機制:隊列和管道。
隊列和管道都是將數據存放於內存中
隊列又是基於(管道+鎖)實現的,可讓咱們從複雜的鎖問題中解脫出來,
咱們應該儘可能避免使用共享數據,儘量使用消息傳遞和隊列,避免處理複雜的同步和鎖問題,並且在進程數目增多時,每每能夠得到更好的可獲展性。

    信號量 —— multiprocess.Semaphore(瞭解)

互斥鎖同時只容許一個線程更改數據,而信號量Semaphore是同時容許必定數量的線程更改數據 。
假設商場裏有4個迷你唱吧,因此同時能夠進去4我的,若是來了第五我的就要在外面等待,等到有人出來才能再進去玩。
實現:
信號量同步基於內部計數器,每調用一次acquire(),計數器減1;每調用一次release(),計數器加1.當計數器爲0時,
acquire()調用被阻塞。這是迪科斯徹(Dijkstra)信號量概念P()和V()的Python實現。信號量同步機制適用於訪問像服務器這樣的有限資源。 信號量與進程池的概念很像,可是要區分開,信號量涉及到加鎖的概念
# 信號量   至關於  鎖 + 計數器
from multiprocessing import Semaphore

sem = Semaphore(4)
sem.acquire()
print(1)
sem.acquire()
print(2)
sem.acquire()
print(3)
sem.acquire()
print(4)
sem.release()
print(5)
信號量簡單實例
import time
import random
from multiprocessing import Process,Semaphore

# def ktv(sem,i):
#     sem.acquire()
#     print('%s走進KTV'%i)
#     time.sleep(random.randint(1,5))
#     print('%s走出KTV'%i)
#     sem.release()

def ktv(sem,i):
    with sem:
        print('%s走進KTV'%i)
        time.sleep(random.randint(1,5))
        print('%s走出KTV'%i)

if __name__ == '__main__':
    sem = Semaphore(4)
    for i in range(10):
        p = Process(target=ktv,args=(sem,i))
        p.start()
經過信號量簡單實現KTV功能

    事件 —— multiprocess.Event(瞭解)

python線程的事件用於主線程控制其餘線程的執行,事件主要提供了三個方法 set、wait、clear。

事件處理的機制:全局定義了一個「Flag」,若是「Flag」值爲 False,那麼當程序執行 event.wait 方法時就會阻塞,若是「Flag」值爲True,那麼event.wait 方法時便再也不阻塞。

clear:將「Flag」設置爲False
set:將「Flag」設置爲True
# wait方法
    # 在事件中有一個標誌
    # 若是這個標誌是True, wait方法的執行效果就是pass
    # 若是這個標誌是False,wait方法的效果就是阻塞
                        # 直到這個標誌變成True
# 控制標誌
    # 判斷標誌的狀態 is_set
    # set方法 將標誌設置爲True
    # clear方法 將標誌設置爲False
import time
from multiprocessing import Event,Process

def func1(e):
    print('start func1')
    e.wait(1)
    print(e.is_set())
    print('end func1')

if __name__ == '__main__':
    e = Event()
    Process(target=func1,args=(e,)).start()
    time.sleep(5)
    e.set()
簡單實例
import time
import random
from multiprocessing import Process,Event
def traffic_light(e):
    print('\033[1;31m紅燈亮\033[0m')
    while True:
        time.sleep(2)
        if e.is_set():
            print('\033[1;31m紅燈亮\033[0m')
            e.clear()
        else:
            print('\033[1;32m綠燈亮\033[0m')
            e.set()

def car(i,e):
    if not e.is_set():
        print('car%s正在等在經過'%i)
        e.wait()
    print('car%s經過'%i)

if __name__ == '__main__':
    e = Event()
    light = Process(target=traffic_light,args=(e,))
    light.daemon = True
    light.start()
    car_lst = []
    for i in range(20):
        p = Process(target=car,args=(i,e))
        p.start()
        time.sleep(random.randint(0,3))
        car_lst.append(p)
    for car in car_lst:car.join()
紅綠燈示例
相關文章
相關標籤/搜索