Python之進程與線程
一.進程
1.什麽是進程
程序並不能單獨運行,只有將程序裝載到內存中,系統為它分配資源才能運行,而這種執行的程序就稱之為進程。程序和進程的區別就在於:程序是指令的集合,它是進程運行的靜態描述文本;進程是程序的一次執行活動,屬於動態概念。
在多道編程中,我們允許多個程序同時加載到內存中,在操作系統的調度下,可以實現並發地執行。這是這樣的設計,大大提高了CPU的利用率。進程的出現讓每個用戶感覺到自己獨享CPU,因此,進程就是為了在CPU上實現多道編程而提出的。
2.有了進程為什麽還要線程?
進程有很多優點,它提供了多道編程,讓我們感覺我們每個人都擁有自己的CPU和其他資源,可以提高計算機的利用率。很多人就不理解了,既然進程這麽優秀,為什麽還要線程呢?其實,仔細觀察就會發現進程還是有很多缺陷的,主要體現在兩點上:
-
進程只能在一個時間幹一件事,如果想同時幹兩件事或多件事,進程就無能為力了。
-
進程在執行的過程中如果阻塞,例如等待輸入,整個進程就會掛起,即使進程中有些工作不依賴於輸入的數據,也將無法執行。
例如,我們在使用qq聊天, qq做為一個獨立進程如果同一時間只能幹一件事,那他如何實現在同一時刻 即能監聽鍵盤輸入、又能監聽其它人給你發的消息、同時還能把別人發的消息顯示在屏幕上呢?你會說,操作系統不是有分時麽?但我的親,分時是指在不同進程間的分時呀, 即操作系統處理一會你的qq任務,又切換到word文檔任務上了,每個cpu時間片分給你的qq程序時,你的qq還是只能同時幹一件事呀。
再直白一點, 一個操作系統就像是一個工廠,工廠裏面有很多個生產車間,不同的車間生產不同的產品,每個車間就相當於一個進程,且你的工廠又窮,供電不足,同一時間只能給一個車間供電,為了能讓所有車間都能同時生產,你的工廠的電工只能給不同的車間分時供電,但是輪到你的qq車間時,發現只有一個幹活的工人,結果生產效率極低,為了解決這個問題,應該怎麽辦呢?。。。。沒錯,你肯定想到了,就是多加幾個工人,讓幾個人工人並行工作,這每個工人,就是線程!
3.
二.線程
1.什麽是線程
在傳統操作系統中,每個進程有一個地址空間,而且默認就有一個控制線程
多線程(即多個控制線程)的概念是,在一個進程中存在多個控制線程,多個控制線程共享該進程的地址空間
進程只是用來把資源集中到一起(進程只是一個資源單位,或者說資源集合),而線程才是cpu上的執行單位,
例如,北京地鐵與上海地鐵是不同的進程,而北京地鐵裏的13號線是一個線程,北京地鐵所有的線路共享北京地鐵所有的資源,比如所有的乘客可以被所有線路拉。
2.為什麽要用線程
多線程指的是,在一個進程中開啟多個線程,簡單的講:如果多個任務共用一塊地址空間,那麽必須在一個進程內開啟多個線程。詳細的講分為4點:
1. 多線程共享一個進程的地址空間
2. 線程比進程更輕量級,線程比進程更容易創建可撤銷,在許多操作系統中,創建一個線程比創建一個進程要快10-100倍,在有大量線程需要動態和快速修改時,這一特性很有用
3. 若多個線程都是cpu密集型的,那麽並不能獲得性能上的增強,但是如果存在大量的計算和大量的I/O處理,擁有多個線程允許這些活動彼此重疊運行,從而會加快程序執行的速度。
4. 在多cpu系統中,為了最大限度的利用多核,可以開啟多個線程(比開進程開銷要小的多)
三.Python並發編程之多進程
1.multiprocessing模塊介紹
python中的多線程無法利用多核優勢,如果想要充分地使用多核CPU的資源(os.cpu_count()查看),在python中大部分情況需要使用多進程。Python提供了非常好用的多進程包multiprocessing。
multiprocessing模塊用來開啟子進程,並在子進程中執行我們定制的任務(比如函數),該模塊與多線程模塊threading的編程接口類似。
multiprocessing模塊的功能眾多:支持子進程、通信和共享數據、執行不同形式的同步,提供了Process、Queue、Pipe、Lock等組件。
需要再次強調的一點是:與線程不同,進程沒有任何共享狀態,進程修改的數據,改動僅限於該進程內。
2.Process類的介紹
-
創建進程的類
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字符的字符串。這個鍵的用途是為涉及網絡連接的底層進程間通信提供安全性,這類連接只有在具有相同的身份驗證鍵時才能成功(了解即可)
3.Process類的使用
part1:創建並開啟子進程的方式
註意:在windows中Process()必須放到# if __name__ == ‘__main__‘:下
由於Windows沒有fork,多處理模塊啟動一個新的Python進程並導入調用模塊。
如果在導入時調用Process(),那麽這將啟動無限繼承的新進程(或直到機器耗盡資源)。
這是隱藏對Process()內部調用的原,使用if __name__ == “__main __”,這個if語句中的語句將不會在導入時被調用。
-
開進程第一種方法
# -*- coding:utf-8 -*- # /user/bin/python import time import random from multiprocessing import Process def eat(name): print("%s eating" % name) time.sleep(random.randint(1,3)) print("%s eat end" % name) if __name__ == ‘__main__‘: p1 = Process(target=eat, args=("alex",)) p2 = Process(target=eat, args=("nickle",)) p3 = Process(target=eat, args=("json",)) p4 = Process(target=eat, args=("tom",)) p1.start() p2.start() p3.start() p4.start() print("主線程") """ 輸出結果: 主線程 alex eating nickle eating json eating tom eating nickle eat end json eat end alex eat end tom eat end """
-
開啟進程第二種方法
# -*- coding:utf-8 -*- # /user/bin/python import time import random from multiprocessing import Process class eat(Process): def __init__(self,name): super().__init__() self.name = name def run(self): print("%s eating" % self.name) time.sleep(random.randint(1,3)) print("%s eat end" % self.name) if __name__ == ‘__main__‘: p1 = eat("alex") p2 = eat("nickle") p3 = eat("json") p4 = eat("tom") p1.start() p2.start() p3.start() p4.start() print("主進程") """ 輸出結果: 主進程 alex eating json eating nickle eating tom eating nickle eat end tom eat end json eat end alex eat end """
part2:Process對象的其他方法或屬性
-
進程對象的方法一:terminate,is_alive
# -*- coding:utf-8 -*- # /user/bin/python from multiprocessing import Process import time import random class eat(Process): def __init__(self,name): super().__init__() self.name = name def run(self): print("%s eating" % self.name) time.sleep(random.randint(1,3)) print("%s eat end" % self.name) if __name__ == ‘__main__‘: p = eat("nickle") p.start() p.terminate() # 關閉進程,不會立即關閉,所以is_alive立刻查看的結果可能還是存活 print(p.is_alive()) # 結果為True time.sleep(random.randint(1,3)) print("go") print(p.is_alive()) # 結果為False """ 輸出結果: True go False """
註意了:p.join(),是父進程在等p的結束,是父進程阻塞在原地,而p仍然在後臺運行
-
進程對象的方法二:p.daemon=True,p.join
# -*- coding:utf-8 -*- # /user/bin/python from multiprocessing import Process import time import random class eat(Process): def __init__(self,name): super().__init__() self.name = name def run(self): print("%s eating" % self.name) time.sleep(random.randint(1,3)) print("%s eat end" % self.name) if __name__ == ‘__main__‘: p = eat("nickle") p.daemon = True # 一定要在p.start()前設置,設置p為守護進程,禁止p創建子進程,並且主進程死,p跟著一起死 p.start() p.join(0.0001) # 等待p停止,等0.0001秒就不再等了 print("go") """ 輸出結果: go """
# -*- coding:utf-8 -*- # /user/bin/python from multiprocessing import Process import time import random def eat(name): print("%s eating" % name) time.sleep(random.randint(1,3)) print("%s eat end" % name) if __name__ == ‘__main__‘: p1 = Process(target=eat, args=("alex",)) p2 = Process(target=eat, args=("nickle",)) p3 = Process(target=eat, args=("json",)) p4 = Process(target=eat, args=("tom",)) 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("主線程") # 簡化方式: # p_lis = [p1, p2, p3, p4] # for p in p_lis: # p.start() # # for p in p_lis: # p.join() """ 輸出結果: alex eating nickle eating json eating tom eating nickle eat end tom eat end alex eat end json eat end 主線程 """View Code
-
進程對象的其他方法:name,id
# -*- coding:utf-8 -*- # /user/bin/python from multiprocessing import Process import time import random class eat(Process): def __init__(self,name): # self.name=name # super().__init__() #Process的__init__方法會執行self.name=Piao-1, # #所以加到這裏,會覆蓋我們的self.name=name #為我們開啟的進程設置名字的做法 super().__init__() self.name = name def run(self): print("%s eating" % self.name) time.sleep(random.randint(1,3)) print("%s eat end" % self.name) if __name__ == ‘__main__‘: p = eat("nickle") p.start() print("主線程") print(p.pid) print(p.name) """ 輸出結果: 主線程 9100 nickle nickle eating nickle eat end """
4.進程間通信(IPC)方式一:隊列
進程彼此之間互相隔離,要實現進程間通信,即IPC,multiprocessing模塊支持兩種形式:隊列和管道,這兩種方式都是使用消息傳遞的
創建隊列的類(底層就是以管道和鎖定的方式實現):
1 Queue([maxsize]):創建共享的進程隊列,Queue是多進程安全的隊列,可以使用Queue實現多進程之間的數據傳遞。
參數介紹:
主要方法:
1 q.put方法用以插入數據到隊列中,put方法還有兩個可選參數:blocked和timeout。如果blocked為True(默認值),並且timeout為正值,該方法會阻塞timeout指定的時間,直到該隊列有剩余的空間。
如果超時,會拋出Queue.Full異常。如果blocked為False,但該Queue已滿,會立即拋出Queue.Full異常。 2 q.get方法可以從隊列讀取並且刪除一個元素。同樣,get方法有兩個可選參數:blocked和timeout。如果blocked為True(默認值),並且timeout為正值,那麽在等待時間內沒有取到任何元素,
會拋出Queue.Empty異常。如果blocked為False,有兩種情況存在,如果Queue有一個值可用,則立即返回該值,否則,如果隊列為空,則立即拋出Queue.Empty異常. 3 4 q.get_nowait():同q.get(False) 5 q.put_nowait():同q.put(False) 6 7 q.empty():調用此方法時q為空則返回True,該結果不可靠,比如在返回True的過程中,如果隊列中又加入了項目。 8 q.full():調用此方法時q已滿則返回True,該結果不可靠,比如在返回True的過程中,如果隊列中的項目被取走。 9 q.qsize():返回隊列中目前項目的正確數量,結果也不可靠,理由同q.empty()和q.full()一樣
其他方法:
1 1 q.cancel_join_thread():不會在進程退出時自動連接後臺線程。可以防止join_thread()方法阻塞 2 2 q.close():關閉隊列,防止隊列中加入更多數據。調用此方法,後臺線程將繼續寫入那些已經入隊列但尚未寫入的數據,但將在此方法完成時馬上關閉。如果q被垃圾收集,將調用此方法。關閉隊列不會在隊列
使用者中產生任何類型的數據結束信號或異常。例如,如果某個使用者正在被阻塞在get()操作上,關閉生產者中的隊列不會導致get()方法返回錯誤。 3 3 q.join_thread():連接隊列的後臺線程。此方法用於在調用q.close()方法之後,等待所有隊列項被消耗。默認情況下,此方法由不是q的原始創建者的所有進程調用。調用q.cancel_join_thread方法可以禁止這種行為
應用:
Python之進程與線程