Python多任務之進程

  • 2019 年 10 月 14 日
  • 筆記

Process多進程

進程的概念

程式是沒有運行的程式碼,靜態的;

進程是運行起來的程式,進程是一個程式運行起來之後和資源的總稱;

程式只有一個,但同一份程式可以有多個進程;例如,電腦上多開QQ;

程式和進程的區別在於有沒有資源,進程有資源而程式沒有資源,進程是一個資源分配的基本單元;
程式在沒運行的時候沒有資源,沒有顯示卡,沒有網卡,等等;雙擊運行後有攝影機,有網速等等,就叫做進程;

進程的狀態

進程狀態圖

  • 就緒態:運行的條件都已經慢去,正在等在cpu執行
  • 執行態:cpu正在執行其功能
  • 等待態:等待某些條件滿足,例如一個程式sleep了,此時就處於等待態

 

使用Process完成多任務

進程的使用步驟和執行緒的使用步驟基本一致;

進程的使用步驟

  1. 導入multiprocessing;
  2. 編寫多任務所所需要的函數;
  3. 創建multiprocessing.Process類的實例對象並傳入函數引用;
  4. 調用實例對象的start方法,創建子執行緒。

進程使用步驟圖示:

進程使用步驟程式碼

import time  import multiprocessing      def sing():      while True:          print("-----sing-----")          time.sleep(1)      def dance():      while True:          print("-----dance-----")          time.sleep(1)      def main():      p1 = multiprocessing.Process(target=sing)      p2 = multiprocessing.Process(target=dance)      p1.start()      p2.start()      if __name__ == "__main__":      main()

使用Process完成多任務.py

運行結果:

-----sing-----  -----dance-----  -----sing-----  -----dance-----  -----sing-----  -----dance-----  ......

 

進程:

  • 主進程有什麼,子進程就會有什麼資源;
  • 執行緒能創建多任務,進程也能創建多任務,但進程耗費的資源比較大;
  • 所以運行的進程數,不一定越多越好;
  • 當創建子進程時,會複製一份主進程的資源,程式碼,記憶體等,但又會有自己不同的地方,比如pid等;
  • 我們可以理解為多進程之間共享程式碼,即只有一份程式碼,但有多個指向同一程式碼的箭頭;
  • 能共享的就共享,不能共享的就拷貝一份;不需要修改的就共享,要修改的時候就給你拷貝一份,這就是寫時拷貝;

 

獲取進程id

獲取進程id程式碼

from multiprocessing import Process  import osdef run_proc():      """子進程要執行的程式碼"""      print('子進程運行中,pid=%d...' % os.getpid())  # os.getpid獲取當前進程的進程號      print('子進程將要結束...')    if __name__ == '__main__':      print('父進程pid: %d' % os.getpid())  # os.getpid獲取當前進程的進程號      p = Process(target=run_proc)      p.start()

 

進程和執行緒對比

進程和執行緒的區別

  • 進程是系統進行資源分配和調度的一個獨立單位;
  • 執行緒是進程的一個實體,是CPU調度和分派的基本單位,即是作業系統調度的單位,它是比進程更小的能獨立運行的基本單位;
  • 一個程式至少有一個進程,一個進程至少有一個執行緒;
  • 執行緒的劃分尺度小於進程(資源比進程少),使得多執行緒程式的並發性高;
  • 進程在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體,從而極大地提高了程式的運行效率;
  • 執行緒不能夠獨立執行,必須依存在進程中;
  • 進程先有,才有的執行緒;
  • 執行緒用資源去做事;
  • 多執行緒能實現多任務是指在一個進程資源裡面有多個箭頭;多執行緒是在同一個資源裡面有多個箭頭執行同一份程式碼;
  • 多進程的多任務是又開啟了一份資源,在這個資源裡面又有一個箭頭;
  • 進程執行方式1:在一份資源裡面有多個箭頭在執行;
  • 進程執行方式2:有多份資源,在每一份資源裡面有一個箭頭執行程式碼;
  • 執行緒執行開銷小,但不利於資源的管理和保護,進程正好相反;
  • 開發中還是多執行緒用的多;

 

通過隊列完成進程間通訊

隊列使用語法

# 創建隊列:  from multiprocessing import Queue  q = Queue(3)  # 往隊列中添加數據:  q.put(xxx)  # 從隊列中獲取數據:  q.get()

通過隊列完成進程間通訊程式碼

from multiprocessing import Queue  import multiprocessing      def download_data(q):      """模擬這是從網上下載數據"""      data = [11, 22, 33]      for i in data:          q.put(i)      print("數據下載完成")      def deal_data(q):      """模擬處理從網上下載下來的數據"""      data_list = []      while True:          data = q.get()          data_list.append(data)          if q.empty():              break      print("處理數據結束,數據為:", data_list)      def main():      q = Queue(3)      p1 = multiprocessing.Process(target=download_data, args=(q,))      p2 = multiprocessing.Process(target=deal_data, args=(q,))      p1.start()      time.sleep(1)      p2.start()      if __name__ == '__main__':      main()

通過隊列完成進程間通訊.py

運行結果:

數據下載完成  處理數據結束,數據為: [11, 22, 33]

 

進程池完成多任務

進程池

進程池的概念

因為進程的創建和銷毀是需要大量的資源的,為了減少消耗,當我們在處理多任務時,比如100個任務,我們可以先創建10個進程,然後用這10個進程來執行者100個任務,就可以重複使用進程,達到節約資源的目的了,而這個就可以使用進程池。

進程池的創建
任務數固定且較少,用普通的進程即可;任務數不確定,且比較多,就用進程池;
進程池不會等待進程執行完畢,我們需要使用po.join()讓主進程等待進程池中的進程執行完;且po.close()必須在join前面;

創建進程池語法

# 創建進程池  from multiprocessing import Pool  po = Pool(3)    # 給進程池傳遞任務和參數  po.asyn(sing, (num,))    # 讓進程池等待子進程執行完  po.close()  po.join()

進程池pool示例

from multiprocessing import Pool  import os, time, random      def worker(msg):      t_start = time.time()      print("%s開始執行,進程號為%d" % (msg, os.getpid()))      # random.random()隨機生成0~1之間的浮點數      time.sleep(random.random() * 2)      t_stop = time.time()      print(msg, "執行完畢,耗時%0.2f" % (t_stop - t_start))      def main():      po = Pool(3)  # 定義一個進程池,最大進程數3      for i in range(0, 10):          # Pool().apply_async(要調用的目標,(傳遞給目標的參數元祖,))          # 每次循環將會用空閑出來的子進程去調用目標          po.apply_async(worker, (i,))        print("----start----")      po.close()  # 關閉進程池,關閉後po不再接收新的請求      po.join()  # 等待po中所有子進程執行完成,必須放在close語句之後      print("-----end-----")      if __name__ == '__main__':      main()

進程池pool示例.py

執行結果:

----start----  0開始執行,進程號為7812  1開始執行,進程號為9984  2開始執行,進程號為1692  1 執行完畢,耗時0.65  3開始執行,進程號為9984  0 執行完畢,耗時1.08  4開始執行,進程號為7812  2 執行完畢,耗時1.82  5開始執行,進程號為1692  4 執行完畢,耗時1.12  6開始執行,進程號為7812  3 執行完畢,耗時1.35  7開始執行,進程號為9984  7 執行完畢,耗時0.11  8開始執行,進程號為9984  6 執行完畢,耗時0.50  9開始執行,進程號為7812  5 執行完畢,耗時0.65  8 執行完畢,耗時0.70  9 執行完畢,耗時0.74  -----end-----

執行結果

 

多進程拷貝文件夾

多任務文件夾copy

步驟思路:

  • 1.獲取用戶要拷貝的文件夾的名字;
  • 2.創建一個新的文件夾;
  • 3.獲取文件夾的所有待拷貝的文件名;listdir()
  • 4.創建進程池;
  • 5.複製原文件夾中的文件,到新文件夾的文件中去;

多任務拷貝文件程式碼

import os  from multiprocessing import Pool      def copy_file(file, old_folder, new_folder):        old_f = open(old_folder+"/"+file, "rb")      data = old_f.read()      old_f.close()        new_f = open(new_folder+"/"+file, "wb")      new_f.write(data)      new_f.close()      print("創建文件成功:", file)      def main():      # 1.獲取要拷貝的文件夾      old_folder = input("請輸入你要拷貝的文件夾:")      # 2.創建新文件夾      new_folder = old_folder + "_復件"      try:          os.mkdir(new_folder)          print("創建文件夾成功")      except Exception as e:          pass      # 3.獲取文件夾中所有待拷貝的文件,listdir()      files_list = os.listdir(old_folder)      # print(files_list)      # 4.創建進程池      po = Pool(5)      for file in files_list:          # 向進程池中添加複製文件的任務          po.apply_async(copy_file, args=(file, old_folder, new_folder))      # 複製原文件夾中的文件,到新文件夾中      po.close()      po.join()      if __name__ == '__main__':      main()

使用進程池拷貝文件夾

在完成文件夾拷貝後,增加了一個需求,顯示拷貝文件的進度條,怎麼辦?

 

多任務拷貝文件並顯示進度條

如果要在進程池中使用Queue,要使用from multiprocessing import Manager ,使用Manager().Queue();

顯示進度條思路:

  1. 創建一個隊列;
  2. 往拷貝文件的函數中傳入隊列,拷貝好一個文件就往q中傳入該文件名;
  3. 在主函數中計算listdir()中的所有文件數量;
  4. 在主函數中定義一個num,初始值為0;
  5. 在主函數中定義一個while true,從q中獲取文件每獲取一個文件們就將num+1
  6. 計算,如果num的值大於等於總文件數量,就break;
  7. 使用已拷貝文件數量num除以總文件數量,即為拷貝的進度,使用開頭r 和end=””讓顯示進度不換行,如下:
print("r已拷貝文件%.2f %%" % (copy_ok_file_num*100/all_file_len), end="")

 

多任務拷貝文件並顯示進度條程式碼

import os  from multiprocessing import Pool, Manager      def copy_file(q, file, old_folder, new_folder):        old_f = open(old_folder+"/"+file, "rb")      data = old_f.read()      old_f.close()        new_f = open(new_folder+"/"+file, "wb")      new_f.write(data)      new_f.close()      q.put(file)      def main():      # 1.獲取要拷貝的文件夾      old_folder = input("請輸入你要拷貝的文件夾:")      # 2.創建新文件夾      new_folder = old_folder + "_復件"      try:          os.mkdir(new_folder)          print("創建文件夾成功")      except Exception as e:          pass      # 3.獲取文件夾中所有待拷貝的文件,listdir()      files_list = os.listdir(old_folder)      # 4.創建進程池      po = Pool(5)      # 5.創建隊列      q = Manager().Queue()      # 6.複製原文件夾中的文件,到新文件夾中      for file in files_list:          # 向進程池中添加複製文件的任務          po.apply_async(copy_file, args=(q, file, old_folder, new_folder))      all_file_len = len(files_list)      po.close()      # po.join()      copy_ok_file_num = 0      while True:          file = q.get()          copy_ok_file_num += 1          print("已拷貝文件%.2f %%" % (copy_ok_file_num*100/all_file_len))          # print("r已拷貝文件%.2f %%" % (copy_ok_file_num*100/all_file_len), end="")          if copy_ok_file_num >= all_file_len:              break      print()      if __name__ == '__main__':      main()

多任務拷貝文件並顯示進度條.py