Python之路(第三十七篇)併發編程:進程、multiprocess模塊、建立進程方式、join()、守護進程

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

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

 

2、multiprocess模塊

python中的多線程沒法利用多核優點,若是想要充分地使用多核CPU的資源(os.cpu_count()查看),在python中大部分狀況須要使用多進程。Python提供了multiprocessing。 multiprocessing模塊用來開啓子進程,並在子進程中執行咱們定製的任務(好比函數),該模塊與多線程模塊threading的編程接口相似。python

  multiprocessing模塊的功能衆多:支持子進程、通訊和共享數據、執行不一樣形式的同步,提供了Process、Queue、Pipe、Lock等組件。linux

​ 須要再次強調的一點是:與線程不一樣,進程沒有任何共享狀態,進程修改的數據,改動僅限於該進程內。編程

Process類的介紹

建立進程的類windows

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

  

參數介紹:安全

  
  group參數未使用,值始終爲None
  ​
  target表示調用對象,即子進程要執行的任務
  ​
  args表示調用對象的位置參數元組,args=(1,2,'egon',)
  ​
  kwargs表示調用對象的字典,kwargs={'name':'egon','age':18}
  ​
  name爲子進程的名稱

  

方法介紹:網絡

  
  p.start():啓動進程,並調用該子進程中的p.run() 
  p.run():進程啓動時運行的方法,正是它去調用target指定的函數,咱們自定義類的類中必定要實現該方法  
  ​
  p.terminate():強制終止進程p,不會進行任何清理操做,若是p建立了子進程,該子進程就成了殭屍進程,使用該方法須要特別當心這種狀況。若是p還保存了一個鎖那麼也將不會被釋放,進而致使死鎖
  p.is_alive():若是p仍然運行,返回True
  ​
  p.join([timeout]):主線程等待p終止(強調:是主線程處於等的狀態,而p是處於運行的狀態)。timeout是可選的超時時間,須要強調的是,p.join只能join住start開啓的進程,而不能join住run開啓的進程 
 

  

屬性介紹:數據結構

  
  p.daemon:默認值爲False,若是設爲True,表明p爲後臺運行的守護進程,當p的父進程終止時,p也隨之終止,而且設定爲True後,p不能建立本身的新進程,必須在p.start()以前設置
  ​
  p.name:進程的名稱
  ​
  p.pid:進程的pid
  ​
  p.exitcode:進程在運行時爲None、若是爲–N,表示被信號N結束(瞭解便可)
  ​
  p.authkey:進程的身份驗證鍵,默認是由os.urandom()隨機生成的32字符的字符串。這個鍵的用途是爲涉及網絡鏈接的底層進程間通訊提供安全性,這類鏈接只有在具備相同的身份驗證鍵時才能成功(瞭解便可)

  

Process類的使用

注意:在windows中Process()必須放到# if name == 'main':下多線程

 

建立並開啓子進程的兩種方式併發

方式一

  
  #方式一:直接用函數
  ​
  import multiprocessing
  # from multiprocessing import Process  這種導入模塊的方式能夠在下面代碼中直接寫Process(target= ,args=)
  import time
  ​
  def hi(name):
      print("hello %s"%name)
      time.sleep(1)
  ​
  if __name__ == "__main__":
      p = multiprocessing.Process(target=hi,args=("nick",))
      p.start()
      p.join()
      print("ending...")

  


方式二

  
  #開啓進程的方式二,在類中啓動進程
  import time
  import multiprocessing
  ​
  class Foo(multiprocessing.Process):  #這裏繼承 multiprocessing.Process類
  ​
      def __init__(self,name):
          super().__init__()
          self.name = name
  ​
      def run(self):
          print("hello %s" % self.name)
          time.sleep(3)
  ​
  if __name__ == "__main__":
      p = Foo("nick")
      p.start()  #這裏執行start()會直接調用類的run()方法
      # p.join()
      print("ending...")

  

 

進程直接的內存空間是隔離的

  
  import multiprocessing
  ​
  n = 100
  ​
  ​
  def work():
      global n
      n = 0
      print("子進程內的n", n)
  ​
  ​
  if __name__ == '__main__':
      p = multiprocessing.Process(target=work)
      p.start()
      print("主進程內的n", n)

  

輸出結果

  
  主進程內的n 100
  子進程內的n 0

  

分析:因爲子進程和主進程是隔離的,因此即便在子進程裏有global關鍵字,主進程同一變量的值也沒變。

Process對象的join方法

在主進程運行過程當中若是想併發地執行其餘的任務,咱們能夠開啓子進程,此時主進程的任務與子進程的任務分兩種狀況

狀況一:在主進程的任務與子進程的任務彼此獨立的狀況下,主進程的任務先執行完畢後,主進程還須要等待子進程執行完畢,而後統一回收資源。

狀況二:若是主進程的任務在執行到某一個階段時,須要等待子進程執行完畢後才能繼續執行,就須要有一種機制可以讓主進程檢測子進程是否運行完畢,在子進程執行完畢後才繼續執行,不然一直在原地阻塞,這就是join方法的做用。

例子

  
  from multiprocessing import Process
  import time
  ​
  def func(args):
      print('-----',args)
      time.sleep(2)
      print("end---")
  ​
  if __name__ == '__main__':
      p = Process(target=func,args=(1,))
      p.start()
      print('哈哈哈哈')
      p.join()#join()的做用是阻塞主進程,使得主進程等待子進程執行完才把本身結束
      print("主進程運行完了")

  

例子2

  
  from multiprocessing import Process
  import time
  import random
  def piao(name):
      print('%s is talking' %name)
      time.sleep(random.randint(1,3))
      print('%s is talking end' %name)
  ​
  p1=Process(target=piao,args=('nick',))
  p2=Process(target=piao,args=('jack',))
  p3=Process(target=piao,args=('pony',))
  p4=Process(target=piao,args=('charles',))
  ​
  p1.start()
  p2.start()
  p3.start()
  p4.start()
  ​
  #疑問:既然join是等待進程結束,那麼像下面這樣寫,進程不就又變成串行的了嗎?
  #固然不是了,必須明確:p.join()是讓誰等?
  #很明顯p.join()是讓主線程等待p的結束,卡住的是主線程而絕非進程p,
  ​
  #詳細解析以下:
  #進程只要start就會在開始運行了,因此p1-p4.start()時,系統中已經有四個併發的進程了
  #而p1.join()是在等p1結束,沒錯p1只要不結束主線程就會一直卡在原地,這也是問題的關鍵
  #join是讓主線程等,而p1-p4仍然是併發執行的,p1.join的時候,其他p2,p3,p4仍然在運行,等#p1.join結束,可能p2,p3,p4早已經結束了,這樣p2.join,p3.join.p4.join直接經過檢測,無需等待
  # 因此4個join花費的總時間仍然是耗費時間最長的那個進程運行的時間
  p1.join()
  p2.join()
  p3.join()
  p4.join()
  ​
  print('主線程')
  ​
  ​
  #上述啓動進程與join進程能夠簡寫爲
  # p_l=[p1,p2,p3,p4]
  #
  # for p in p_l:
  #     p.start()
  #
  # for p in p_l:
  #     p.join()
 

  

Process對象的其餘方法或屬性

進程對象的其餘方法:terminate與is_alivename與pid

例子

  
  import multiprocessing
  import time
  ​
  ​
  def task(name, t):
      print("進程正在運行。。。%s--%s" % (name, time.asctime()))
      time.sleep(t)
      print("進程結束了%s--%s" % (name, time.asctime()))
  ​
  ​
  if __name__ == "__main__":
      p1 = multiprocessing.Process(target=task, args=("nick", 5), name="進程1")  # 注意這裏的參數name不是函數的參數
      p2 = multiprocessing.Process(target=task, args=("nicholas", 2), name="進程2")
      p_list = []
      p_list.append(p1)
      p_list.append(p2)
      print("進程是否存活", p1.is_alive())
      for p in p_list:
          p.start()
          print(p.name)  # 進程.name輸出進程的名稱
          print(p.name, p.pid)  # 進程對象.pid也能夠輸出子進程的ID號,
      print("進程是否存活", p1.is_alive())  # 判斷子進程是否存活
      # p2.terminate()#無論進程是否執行完,馬上結束進程
      for p in p_list:
          p.join()
      print("ending...")

  

經過os模塊查看pid和ppid

import multiprocessing
import os

class Foo(multiprocessing.Process):

    def __init__(self,name):
        super().__init__()
        self.name = name

    def run(self):
        print("hi %s,子進程號是%s"%(self.name,os.getpid()))#輸出當前的進程的pid


if __name__ == "__main__":
    p = Foo("nick")
    p.start()
    print("主進程是%s"%os.getppid())  #這裏的主進程好就是執行這個py文件的程序,這裏是pycharm,
    # 若是用命令終端執行py文件則主進程是命令終端的號
    #os.getppid()是輸出當前進程的父進程pid號
    #os.getpid()是輸出當前的進程的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進程就會出面處理它的一切善後工做。所以孤兒進程並不會有什麼危害。     三:殭屍進程危害場景: ​   例若有個進程,它按期的產 生一個子進程,這個子進程須要作的事情不多,作完它該作的事情以後就退出了,所以這個子進程的生命週期很短,可是,父進程

只管生成新的子進程,至於子進程 退出以後的事情,則一律漠不關心,這樣,系統運行上一段時間以後,系統中就會存在不少的僵死進程,假若用ps命令查看的話,

就會看到不少狀態爲Z的進程。 嚴格地來講,僵死進程並非問題的根源,罪魁禍首是產生出大量僵死進程的那個父進程。所以,當咱們尋求如何消滅系統中大量的

僵死進程時,答案就是把產生大 量僵死進程的那個元兇槍斃掉(也就是經過kill發送SIGTERM或者SIGKILL信號啦)。槍斃了元兇進程以後,它產生的僵死進程

就變成了孤兒進 程,這些孤兒進程會被init進程接管,init進程會wait()這些孤兒進程,釋放它們佔用的系統進程表中的資源,這樣,這些已經僵死的孤兒進程

就能瞑目而去了。

  

 

守護進程

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

主進程建立守護進程

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

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

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

 

例子1

  
  from multiprocessing import Process
  ​
  import time
  def foo():
      print(123)
      time.sleep(1)
      print("end123")
  ​
  def bar():
      print(456)
      time.sleep(3)
      print("end456")
  ​
  if __name__ == '__main__':
      p1=Process(target=foo)
      p2=Process(target=bar)
  ​
      p1.daemon=True ##必定要在p1.start()前設置,設置p1爲守護進程,
      # 禁止p1建立子進程,而且父進程代碼執行結束,p1即終止運行
      p1.start()
      p2.start()
      print("main-------") #只要終端打印出這一行內容,那麼守護進程p1也就跟着結束掉了

  

例子2

  
  # 主進程代碼運行完畢,守護進程就會結束
  from multiprocessing import Process
  ​
  import time
  ​
  ​
  def foo():
      print(123)
      time.sleep(1)
      print("end123")
  ​
  ​
  def bar():
      print(456)
      time.sleep(3)
      print("end456")
  ​
  ​
  if __name__ == '__main__':
  ​
      p1 = Process(target=foo)
      p2 = Process(target=bar)
  ​
      p1.daemon = True
      p1.start()
      p2.start()
      print("main-------")  # 打印該行則主進程代碼結束,則守護進程p1應該被終止,
      # 可能會有p1任務執行的打印信息123,由於主進程打印main----時,p1也執行了,可是隨即被終止
      #這時p1可能會被執行,也可能不會被執行

  

 

參考資料

[1]http://www.cnblogs.com/Anker/p/3271773.html

相關文章
相關標籤/搜索