從零開始的python世界的闖蕩之視頻學習篇 第十五話 多進程
#學校事情多,然後還要看視頻,寫實驗,最後寫筆記。 時間過得太快了#但絕不能為了完成任務而發表文章。即使幾天一篇,也不能隨便瞎糊弄。#好的,本篇是講解多進程。
一.多任務的概念
#多任務: 就像筆者我一樣,既要上課,又要看學習視頻,還得寫作業又得寫筆記,這就是多任務。# 同一個人在同一個時間段內需要干多個任務。# 換在電腦中,多任務就是一邊聽音樂,一邊打遊戲一樣。 同時的讓電腦幹多個事情。
二.相關名詞的概念
#1. 程序: 就是咱們用某種語言編寫好的代碼。#2. 進程: 在特定的環境下,執行程序,即,執行咱們寫好的代碼。#3. 並行: 擁有足夠多的處理器,去單獨的處理任務。比如有4個任務需要同時處理,而我們有5個處理器 那麼,此時一個處理器處理一個任務,這就是並行。#4. 並發: 一個處理器,去執行多個任務。在結果上看,是同時去完成任務,其實是執行一個任務一段 時間後,暫停這個任務,去執行另一個任務,然後再換回來。 這就是並發。
三.多進程---》利用fork()創建子進程
#當我們想要同時的去完成多個任務時,單進程模式肯定會影響速度,比如,咱們去下載多個視頻,如果#官方設定,每個視頻最高速度只能1Mb/s的話,如果單個進程去下載10個視頻,就要等一個視頻下好後#再去下第二個視頻。這樣難免會浪費時間。 當我們採用多進程的時候,我們可以同時去下載10個視頻,#這樣效率是不是達到了10倍呢。
#1.進程創建 os.fork()-->底層的創建進程方式
#fork(): 用來創建一個子進程。 但是只能夠在linux或者mac系統上使用,windows是不能使用的。# 並且這個方法會返回一個值,如果是子進程的話,返回值為0.#舉個例子:

#答疑:那是因為咱們利用fork()創建了一個子進程,此時將有兩個進程在執行if語句。並且,fork()# 會返回一個0值,代表著我是子進程。而主進程將不是0值。那麼就可以執行if中的所有代碼。#

#由上面的圖示可以知道,子進程是在fork()後面開始執行代碼的。#還有一個疑問,解釋器是如何知道哪個是主進程,哪個是子進程呢?#答疑: 在進程中,他們都有一個唯一的編號,稱之為pid,並且呢,在創建子進程的時候,還會返回一個值 用來標識,我是子進程。這個值為0.上圖也是利用這個特性,來讓主進程和子進程列印不同的內容
#2.獲取進程的ID號
#os.getpid() :這個是用來獲取本進程的pid號#os.getppid() : 這個是用來獲取本進程的父pid號(特別說明,父進程就是籠統的主進程,誰創建了 這個子進程,誰就是父進程)#使用舉例:

#特別說明,pid是唯一的,不存在重複。
#3.父進程和子進程誰快?
#這個問題問得好,答案也是比較簡單的,不同的系統,由於任務調度演算法不同,所以並不好判斷父進程 和子進程運行順序,有的情況下,子進程快,有些是父進程快。#如果你想要子進程運行順序比父進程快,可以在父進程中加上一個time.sleep()強制讓他暫停。
#4.多進程中的全局變數
#父進程創建子進程,那麼,子進程中擁有父進程的什麼東西呢?#

#那麼,咱們在子進程中修改這個全局變數,看看會有什麼結果。

#不理解沒關係,接著看

#5.多個fork的理解
#當我們使用多個fork時,會產生什麼現象呢?

#答疑:需要了解到子進程是從什麼時候開始執行代碼的,其實只有4個進程,說6個的都是錯誤的哦 請看下圖講解上例子

#6.小練習,猜猜下面會列印幾次,有幾個進程
#最好用畫圖解釋#!/usr/bin/env python3import os import timeos.fork()os.fork()os.fork()print("---------1------")time.sleep(0.2)#答案:

#圖示如下:

四.多進程---multiprocessing模塊
#由上面可以知道,os.fork() 只能使用於linuc和mac環境下,那麼,這就違背了python良好的移植性。#畢竟,windows都無法使用,造成的影響還是有的。 #鑒於上面一點,python的開發者就寫了一個multiprocessing模塊#裡面有個Process類,可以在任何平台上進行創建子進程。#好的,接下來咱們講解Process這個類。
#1.利用Process類來創建子進程
#multiprocessing.Process([group [, target [, name [, args [, kwargs]]]]])#先講解參數: group:默認值為None,應該一直為None,他只是僅僅為了與線程兼容而存在,一般用不到 target:需要讓子程序進行的操作,傳遞一個函數。默認不寫為Process對象中的 run()方法。 name:子進程的別名 args: 應該為一個元組,這是用來傳遞進入函數的參數,也就是給子進程的參數。 kwargs: 這是一個字典,同args一樣的作用。#Process返回一個對象,假設為p。#對象擁有如下幾種方法: 1.p.is_alive():判斷子進程是否在運行,返回True代表在運行,False為停止了 2.p.join([timeout]):等待進程實例完成,或者等待指定時間。可以傳遞一個參數 單位是s. 默認是一直等待進程實例完成。 3.p.start(): 開始運行進程實例(創建子進程運行) 4.p.run():在創建對象的時候,如果沒有傳遞target值,那麼就運行這個函數 5.p.terminate():不管子進程是否運行完,立即結束它。#對象還有如下兩個屬性: 1.name:子進程的別名,默認為Process-n 2.pid: 子進程的Pid號。唯一#好的,講了這麼多,實練實練:#

#現在來看看其屬性:

#2.利用類的形式去創建子進程
#上面咱們是直接創建Process對象去創建子進程。#現在咱們利用類的方式來創建子進程#

#3.進程池
#進程池:當我們的需求只需要創建幾個子進程,我們可以使用Process去創建# 但是如果我們需要上百個上千個子進程,如果此時還需要Process去創建的話,效率就太低了。# 此時咱們需要利用multiprocessing 模塊中的pool去創建。 #
#3.1.pool類:
#pool和Process一樣也是一個類,咱們實際上是實例一個對象。#pool(max_process_num): 返回一個對象,max_process_num代表著最大進程數量# 特別解釋,進程池,代表著有個最大容納量,也就是max_process_num# 當已經有max_process_num個進程在運行了,如果咱們還往裡面添加進程# 那麼就會在這暫停,等待空閑位置,然後再加進去。#pool對象的方法: 1.p.apply_async(func,[args,[kwargs]]): 非堵塞的往進程池中添加進程,func 指的是子進程運行的函數,args是一個元組,傳遞函數的參數。kwargs是個字典,同理 2.p.apply(func,[args,[kwargs]):z阻塞的往進程池中添加進程。 3.p.close():關閉進程池,使其不能再往裡面添加進程 4.p.join():主進程阻塞,等待執行完畢,或者等待指定的時間,必須在close或者 terminate後面使用。 5.p.terminate():立即結束子進程#什麼是阻塞?#答疑:阻塞就是需要等你完成後,才執行下一個進程。就相當於按順序執行。#開始舉例子,先來個不阻塞的:

#現在咱們來看看堵塞式的

四.多種創建子進程方式的比較
#1. 基本上是不會去使用os.fork()去創建子進程的。因為它是一個較為底層的方式,並且還無法在# windows上使用。#2. 如果創建的子進程比較少,咱們可以使用Process的方式去進行創建。#3. 如果要大量的去創建子進程的話,那麼咱們需要利用pool進程池。特別說明:#1.os.fork()創建的子進程是從os.fork()下一行的代碼開始進行,並且和主程序執行的代碼一樣。#2.而Process和 pool 創建的子進程,是給他一個單獨的函數,裡面寫了子進程該執行的操作。#3.一般情況下,進程之間是相互獨立,互不干擾的。
五.進程間的通信
#前面咱們已經說過,進程之間是相互獨立的。互不干擾,那麼假如有一個需求。在同一台電腦上,#將一個文件內容進行拷貝。 想要使用多進程的方式,那麼仔細思考,這能實現嗎?#答疑: 假如我們用一個進程進行獲取文件的內容,另一個進程進行寫入文件的內容,是不是可以使用 多進程完成了呢?但是關鍵是,進程之間是相互獨立的,數據不共享。這可怎麼辦?#再次答疑: 為了解決進程之間數據共享的問題,Python中有好幾種方式,目前先講解利用multiprocessing 模塊中的Queue 來進行進程之間的數據共享。#Queue 就好像是兩個進程之間的橋樑,通過這個橋樑,數據就可以在進程之間進行交流了。
#1.Queue
#Queue是multiprocssing模塊中的一個類,他是用來實現多進程的數據傳遞。 它本身是一個消息隊列程序。#隊列:就是類似咱們排隊買飯一樣,先排隊先買飯,俗稱先進先出的一種數據結構。#使用方式: Queue(max_num): 實例化Queue類一個對象,傳遞的參數是能夠容納最大數量的消息,如果# max_num值未設置或者是負值的話,代表著這個隊列的容量將是無限的。# 直到撐破你的內存。#Queue對象的方法: 1.q.qsize():返回一個值,查看消息隊列中還有多少條數據 2.q.empty():判斷隊列是否為空,如果為空的話,那麼就返回True,否則為False 3.q.full():判讀隊列是否飽滿,如果滿的話,返回True,否則為False 4.q.put(item,[block[, timeout]]): 將item數據添加到隊列中,block默認為True; timeout為設置等待時間,如果未設置就是一直等待到可以執行為止。 特別的:<一> 如果block=True,未設置timeout,如果隊列滿了,那麼會在這兒阻塞 直到消息隊列騰出空間,再繼續執行。如果設置了timeout,則只會等待 timeout時間,如果還沒有位置,那麼就會拋出異常。 <二> 如果block=False,當沒有空間了,就立馬拋出異常 5.q.put_nowait(item):效果如同 q.put(item,block=False) 6.q.get([block[,timeout]): 從消息隊列中取出一條消息,此時隊列中的消息被移除。 特別的: <一>如果block=True,未設置timeout,如果隊列空了,那麼會在這兒阻塞; 直到消息隊列中有信息後,再執行。 如果設置了timeout,那麼會等待 timeout時間,如果還沒有消息,那麼就會拋出異常 <二>如果block=False,那麼當隊列中沒有消息後,立馬拋出異常。 7.q.get_nowait(): 效果如同 q.get(block=False) #好的,了解以上內容後,咱們通過一個簡單的例子看效果:

#2.進程池中的隊列
#進程池中的進程之間的數據交流,並不是利用multiprocessing.Queue實例化的對象。#而是使用multiprocessing.Manager.Queue去實例化對象。#其擁有的方法和之前隊列的方法類似,並沒有多大的改動。#舉例:

六.利用多進程實現文件的拷貝
#本節作為文章的練習#答案:#!/usr/bin/env python3from multiprocessing import Manager,Poolimport time def read_file(q,file_name): try: with open(file_name) as f: q.put(f.read()) print("文件加入隊列完畢!") except FileNotFoundError : print("沒有該文件")def write_file(q,file_name): time.sleep(0.2) if q.empty(): print("隊列中無信息,拷貝失敗") else: with open("[備份]"+file_name,"w") as f: f.write(q.get()) print("文件寫入完畢")if __name__ =="__main__": p = Pool(2) q = Manager().Queue() file_name = input("請輸入文件的名字:") print("開始進行文件備份") p.apply_async(read_file, (q, file_name)) p.apply_async(write_file, (q, file_name)) p.close() p.join() print("程序執行完畢")

推薦閱讀:
