1 Python多線程
1.1 GIL 全局解釋器鎖
其他語(yǔ)言,CPU是多核時(shí)是支持多個(gè)線程同時(shí)執(zhí)行。但在Python中,無(wú)論是單核還是多核,同時(shí)只能由一個(gè)線程在執(zhí)行。其根源是GIL的存在。GIL的全稱(chēng)是Global Interpreter Lock(全局解釋器鎖),來(lái)源是Python設(shè)計(jì)之初的考慮,為了數(shù)據(jù)安全所做的決定。某個(gè)線程想要執(zhí)行,必須先拿到GIL,我們可以把GIL看作是“通行證”,并且在一個(gè)Python進(jìn)程中,GIL只有一個(gè)。拿不到通行證的線程,就不允許進(jìn)入CPU執(zhí)行。
GIL與python解釋器有關(guān),目前常用的Python的解釋器有:
- CPython:CPython是用C語(yǔ)言實(shí)現(xiàn)的Python解釋器。 作為官方實(shí)現(xiàn),它是最廣泛使用的Python解釋器。
- PyPy:PyPy是用RPython實(shí)現(xiàn)的解釋器。RPython是Python的子集, 具有靜態(tài)類(lèi)型。這個(gè)解釋器的特點(diǎn)是即時(shí)編譯,支持多重后端(C, CLI, JVM)。PyPy旨在提高性能,同時(shí)保持最大兼容性(參考CPython的實(shí)現(xiàn))。
- Jython:Jython是一個(gè)將Python代碼編譯成Java字節(jié)碼的實(shí)現(xiàn),運(yùn)行在JVM (Java Virtual Machine) 上。另外,它可以像是用Python模塊一樣,導(dǎo)入并使用任何Java類(lèi)。
GIL只在CPython中才有,而在PyPy和Jython中是沒(méi)有GIL的。
每次釋放GIL鎖,線程進(jìn)行鎖競(jìng)爭(zhēng)、切換線程,會(huì)消耗資源。這就導(dǎo)致打印線程執(zhí)行時(shí)長(zhǎng),會(huì)發(fā)現(xiàn)耗時(shí)更長(zhǎng)的原因。
并且由于GIL鎖存在,Python里一個(gè)進(jìn)程永遠(yuǎn)只能同時(shí)執(zhí)行一個(gè)線程(拿到GIL的線程才能執(zhí)行),這就是為什么在多核CPU上,Python 的多線程效率并不高的根本原因。
1.2 創(chuàng)建多線程
Python提供兩個(gè)模塊進(jìn)行多線程的操作,分別是thread
和threading
,前者是比較低級(jí)的模塊,用于更底層的操作,一般應(yīng)用級(jí)別的開(kāi)發(fā)不常用。
Python還提供了線程池函數(shù)ThreadPoolExecutor,它是 concurrent.futures 模塊中的一個(gè)類(lèi),其主要作用是為多線程執(zhí)行提供高級(jí)別的接口。
以下是關(guān)于 ThreadPoolExecutor 的一些重要特性:
- 線程池管理: ThreadPoolExecutor 可以創(chuàng)建一個(gè)線程池,并自動(dòng)管理其中的線程。當(dāng)你提交一個(gè)任務(wù)給 ThreadPoolExecutor 時(shí),它會(huì)選擇一個(gè)空閑的線程去執(zhí)行這個(gè)任務(wù)。如果所有線程都在忙碌,則任務(wù)會(huì)等待直到有線程變?yōu)榭臻e。
- 任務(wù)返回值處理: 當(dāng)你提交一個(gè)任務(wù)給 ThreadPoolExecutor 時(shí),它會(huì)立即返回一個(gè) Future 對(duì)象。通過(guò)這個(gè) Future 對(duì)象,你可以查詢(xún)?nèi)蝿?wù)的狀態(tài)(是否完成,是否成功),獲取任務(wù)的返回值,或者等待任務(wù)完成。
- 任務(wù)異常處理: 如果任務(wù)拋出了一個(gè)異常,ThreadPoolExecutor 將不會(huì)立即拋出這個(gè)異常。相反,當(dāng)你試圖通過(guò) Future 對(duì)象獲取任務(wù)的結(jié)果時(shí),異常才會(huì)被拋出。這使得你可以更好地控制何時(shí)處理這些異常。
- 并行和異步執(zhí)行: ThreadPoolExecutor 允許你在多個(gè)線程中并行地執(zhí)行多個(gè)任務(wù),從而實(shí)現(xiàn)異步操作。這對(duì)于IO密集型任務(wù)尤其有益,比如網(wǎng)絡(luò)請(qǐng)求或文件讀寫(xiě)等。
- 資源清理: 當(dāng) ThreadPoolExecutor 離開(kāi) with 語(yǔ)句塊時(shí),它會(huì)等待所有的任務(wù)都完成,然后釋放所有的資源。這包括結(jié)束所有的線程,釋放所有的鎖,等等。這種機(jī)制可以保證資源被正確地清理。
總的來(lái)說(shuō),ThreadPoolExecutor 提供了一種高級(jí)、方便、安全的方式來(lái)進(jìn)行多線程編程。
- 方法1:直接使用
threading.Thread()
import threading
# 這是你要多線程執(zhí)行的函數(shù)
def worker(num):
"""thread worker function"""
print('Worker: %s' % num)
# 創(chuàng)建線程列表
threads = []
# 啟動(dòng)5個(gè)線程來(lái)運(yùn)行worker函數(shù)
for i in range(5):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
# 等待所有線程完成
for thread in threads:
thread.join()
print("All threads completed.")
- 方法2:繼承threading.Thread來(lái)自定義線程類(lèi),重寫(xiě)run方法
import threading
class MyThread(threading.Thread):
def __init__(self, n):
super(MyThread, self).__init__() # 重構(gòu)run函數(shù)必須要寫(xiě)
self.n = n
def run(self):
print("current task:", n)
if __name__ == "__main__":
t1 = MyThread("thread 1")
t2 = MyThread("thread 2")
t1.start()
t2.start()
1.3 join函數(shù)
在Python中,join()
方法用于等待線程執(zhí)行完畢。它使主線程等待其他子線程完成任務(wù)后再繼續(xù)執(zhí)行。join()
方法被調(diào)用時(shí),主線程會(huì)阻塞,直到調(diào)用它的線程執(zhí)行完畢。
以下是一個(gè)例子,演示了join()
方法的作用:
import threading
import time
def task():
print("Thread started")
time.sleep(2) # 模擬線程執(zhí)行耗時(shí)操作
print("Thread finished")
if __name__ == "__main__":
t = threading.Thread(target=task)
t.start()
print("Main thread continues executing")
t.join() # 等待線程t執(zhí)行完畢
print("Main thread resumed after thread t finished")
在上面的例子中,我們創(chuàng)建了一個(gè)子線程t
,并在主線程中啟動(dòng)它。子線程執(zhí)行task()
函數(shù),在函數(shù)中模擬了一個(gè)耗時(shí)操作time.sleep(2)
。主線程在啟動(dòng)子線程后會(huì)立即繼續(xù)執(zhí)行,并打印出"Main thread continues executing"。
然而,我們?cè)谥骶€程的下一個(gè)步驟中調(diào)用了t.join()
。這意味著主線程會(huì)等待子線程執(zhí)行完畢后再繼續(xù)執(zhí)行。在本例中,主線程會(huì)阻塞,直到子線程執(zhí)行完畢。
當(dāng)子線程完成任務(wù)后,會(huì)打印出"Thread finished"。此時(shí)主線程會(huì)繼續(xù)執(zhí)行,并打印出"Main thread resumed after thread t finished"。
通過(guò)調(diào)用t.join()
,我們確保了主線程等待子線程完成后再繼續(xù)執(zhí)行,這對(duì)于需要線程之間協(xié)調(diào)和同步的情況非常有用。
1.4 線程同步與互斥鎖
線程之間數(shù)據(jù)是共享的。當(dāng)多個(gè)線程對(duì)某一個(gè)共享數(shù)據(jù)進(jìn)行操作時(shí),就需要考慮到線程安全問(wèn)題。在Python的threading
模塊中,Lock
類(lèi)是用于創(chuàng)建鎖對(duì)象的工具,它允許線程之間進(jìn)行同步。
下面是一個(gè)使用Lock
類(lèi)的例子:
import threading
shared_resource = 0 # 共享資源
lock = threading.Lock() # 創(chuàng)建一個(gè)鎖對(duì)象
def increment():
global shared_resource
for _ in range(100000):
lock.acquire() # 獲取鎖
shared_resource += 1
lock.release() # 釋放鎖
if __name__ == "__main__":
threads = []
for _ in range(5):
t = threading.Thread(target=increment)
threads.append(t)
t.start()
for t in threads:
t.join()
print("Final value of shared_resource:", shared_resource)
在上面的例子中,我們創(chuàng)建了一個(gè)共享資源shared_resource
,它的初始值為0。我們還創(chuàng)建了一個(gè)Lock
對(duì)象lock
。
然后,我們定義了一個(gè)increment
函數(shù),它通過(guò)循環(huán)多次增加共享資源的值。在每次增加之前,我們使用lock.acquire()
獲取鎖,以確保當(dāng)前線程獨(dú)占對(duì)共享資源的訪問(wèn)。完成增加后,我們使用lock.release()
釋放鎖。
在主程序中,我們創(chuàng)建了5個(gè)線程,并將它們添加到一個(gè)線程列表中。然后,我們啟動(dòng)這些線程,并等待它們執(zhí)行完畢。
最后,我們打印出最終的shared_resource
的值。由于使用了鎖來(lái)保護(hù)共享資源的訪問(wèn),每個(gè)線程都會(huì)依次獲取鎖并增加共享資源的值,從而保證了最終結(jié)果的正確性。
通過(guò)使用Lock
類(lèi),我們可以在多線程環(huán)境中實(shí)現(xiàn)對(duì)共享資源的安全訪問(wèn)和修改,避免了競(jìng)態(tài)條件(race condition)的發(fā)生。
1.4.1 競(jìng)態(tài)條件
競(jìng)態(tài)條件(Race Condition)是指多個(gè)并發(fā)執(zhí)行的線程或進(jìn)程在訪問(wèn)共享資源或執(zhí)行操作時(shí)的不確定性和不可預(yù)測(cè)性。競(jìng)態(tài)條件會(huì)導(dǎo)致程序在多線程環(huán)境下產(chǎn)生錯(cuò)誤的結(jié)果或出現(xiàn)異常行為。
競(jìng)態(tài)條件發(fā)生的原因是由于多個(gè)線程或進(jìn)程在沒(méi)有正確同步的情況下同時(shí)訪問(wèn)共享資源或執(zhí)行操作,且其執(zhí)行順序無(wú)法確定。具體來(lái)說(shuō),當(dāng)多個(gè)線程或進(jìn)程對(duì)共享資源進(jìn)行讀寫(xiě)操作時(shí),其執(zhí)行順序可能導(dǎo)致互相干擾、相互覆蓋或產(chǎn)生不一致的結(jié)果。
以下是一個(gè)簡(jiǎn)單的競(jìng)態(tài)條件示例:
import threading
counter = 0
def increment():
global counter
for _ in range(100000):
counter += 1
if __name__ == "__main__":
threads = []
for _ in range(5):
t = threading.Thread(target=increment)
threads.append(t)
t.start()
for t in threads:
t.join()
print("Final value of counter:", counter)
在上述示例中,我們創(chuàng)建了一個(gè)全局計(jì)數(shù)器counter
并定義了一個(gè)increment
函數(shù),該函數(shù)使用循環(huán)遞增計(jì)數(shù)器的值。我們創(chuàng)建了5個(gè)線程,每個(gè)線程都會(huì)調(diào)用increment
函數(shù)。
由于多個(gè)線程同時(shí)對(duì)counter
進(jìn)行寫(xiě)操作(遞增),競(jìng)態(tài)條件就會(huì)出現(xiàn)。因?yàn)榫€程之間的執(zhí)行順序是不確定的,可能會(huì)發(fā)生如下情況:
- 線程 A 讀取counter的值為 0。
- 線程 B 讀取counter的值為 0。
- 線程 A 遞增counter的值為 1。
- 線程 B 遞增counter的值為 1。
- 最終的結(jié)果是counter的值為 1,而不是預(yù)期的 2。
這個(gè)示例展示了競(jìng)態(tài)條件的問(wèn)題,多個(gè)線程并發(fā)地修改共享資源導(dǎo)致最終結(jié)果的不確定性。
1.5 可重入鎖(遞歸鎖)
為了滿足在同一線程中多次請(qǐng)求同一資源的需求,Python提供了可重入鎖(RLock)。RLock內(nèi)部維護(hù)著一個(gè)Lock和一個(gè)counter變量,counter記錄了acquire 的次數(shù),從而使得資源可以被多次require。直到一個(gè)線程所有的acquire都被release,其他的線程才能獲得資源。具體用法如下:
1.6 python多線程相比其他語(yǔ)言的優(yōu)勢(shì)
關(guān)于Python多線程與其他編程語(yǔ)言相比的優(yōu)勢(shì),以下幾點(diǎn)可能值得注意:
- 簡(jiǎn)潔性:Python的語(yǔ)法簡(jiǎn)潔明了。對(duì)于多線程編程來(lái)說(shuō),Python提供了 threading 和 concurrent.futures 等模塊,使得開(kāi)發(fā)者可以很容易地創(chuàng)建和管理線程。
- 高級(jí)特性:Python提供了許多高級(jí)工具來(lái)處理多線程編程中的復(fù)雜問(wèn)題,例如隊(duì)列(queue),事件(event),鎖(lock)和條件變量(condition)等。
- 異常處理:Python的線程庫(kù)允許在子線程中捕獲并傳播異常,這樣主線程可以知道任務(wù)是否成功完成。
- 豐富的庫(kù)支持:Python有大量的第三方庫(kù),很多庫(kù)都支持多線程或者異步操作,例如 requests 庫(kù)就可以非常容易地配合 concurrent.futures 來(lái)進(jìn)行并發(fā)的HTTP請(qǐng)求。
然而,需要注意的是,Python的多線程并不適合所有場(chǎng)景。由于Python的全局解釋器鎖(GIL)的存在,Python的多線程在計(jì)算密集型任務(wù)上并不能實(shí)現(xiàn)真正意義上的并行執(zhí)行 (即同一時(shí)間點(diǎn),有多個(gè)線程同時(shí)被多個(gè)CPU核心執(zhí)行)。如果你的程序是計(jì)算密集型的,那么使用多進(jìn)程(multiprocessing 模塊)或者其它能夠避免 GIL 問(wèn)題的方法可能會(huì)更加有效。
1.6 queue
Python 的 queue 模塊提供了一種線程安全的隊(duì)列數(shù)據(jù)結(jié)構(gòu),用于在多線程環(huán)境中進(jìn)行線程間通信和數(shù)據(jù)傳遞。使用 queue 可以解決多線程編程中的許多常見(jiàn)問(wèn)題,以下是它的主要作用:
- 線程間通信: 隊(duì)列通常用于在生產(chǎn)者和消費(fèi)者線程之間進(jìn)行通信。生產(chǎn)者線程可以生成數(shù)據(jù)并將其放入隊(duì)列,而消費(fèi)者線程可以從隊(duì)列中獲取并處理數(shù)據(jù)。這種模式可以有效地對(duì)多線程進(jìn)行解耦。
- 線程同步: 隊(duì)列還可以用于同步多個(gè)線程的工作。例如,一個(gè)線程可能需要等待另一個(gè)線程完成特定任務(wù)后才能繼續(xù)執(zhí)行。這可以通過(guò)在第一個(gè)線程中獲取隊(duì)列項(xiàng)來(lái)實(shí)現(xiàn),其中隊(duì)列項(xiàng)由第二個(gè)線程在完成任務(wù)后添加。
- 數(shù)據(jù)安全性: Python 的 queue 是線程安全的,也就是說(shuō),無(wú)論何時(shí)只有一個(gè)線程可以讀取或?qū)懭腙?duì)列。這意味著你不必自己管理鎖或其他低級(jí)同步機(jī)制,降低了出錯(cuò)的可能性。
- 限制內(nèi)存使用: 隊(duì)列可以設(shè)置最大長(zhǎng)度(即隊(duì)列能容納的元素?cái)?shù)量)。這對(duì)于控制內(nèi)存使用非常有用,特別是當(dāng)生產(chǎn)者線程比消費(fèi)者線程更快時(shí),可以防止隊(duì)列中堆積過(guò)多的元素導(dǎo)致內(nèi)存耗盡。
- 任務(wù)調(diào)度: 通過(guò)分別使用優(yōu)先級(jí)隊(duì)列、LIFO隊(duì)列(即棧)和FIFO隊(duì)列(默認(rèn)),可以實(shí)現(xiàn)各種類(lèi)型的任務(wù)調(diào)度策略。
因此,Python 的 queue 在多線程編程中起著至關(guān)重要的作用,尤其在需要在線程之間安全地共享數(shù)據(jù)或同步線程操作時(shí)。
1.7 condition
Python中的Condition對(duì)象是threading模塊中的同步原語(yǔ)之一。它通常用于管理那些需要等待某些條件成立才能執(zhí)行的線程,從而允許某些線程在滿足特定條件時(shí)喚醒其他線程。
以下是關(guān)于Condition對(duì)象的一些重要特性:
- 鎖定和解鎖: 和Lock或RLock對(duì)象一樣,Condition也支持acquire()和release()方法來(lái)控制對(duì)共享資源的訪問(wèn)。
- 等待條件: Condition對(duì)象提供了一個(gè)wait()方法,使得線程可以等待特定的條件成立。當(dāng)調(diào)用wait()方法時(shí),線程會(huì)釋放鎖并進(jìn)入睡眠狀態(tài),等待被其他線程喚醒。
- 喚醒線程: Condition對(duì)象有兩個(gè)方法,notify()和notify_all(),用于喚醒等待該Condition的線程。notify(n)喚醒n個(gè)等待的線程,notify_all()則喚醒所有等待的線程。
以下是一個(gè)簡(jiǎn)單的例子,說(shuō)明了如何使用Condition對(duì)象:
import threading
# 創(chuàng)建一個(gè) Condition 對(duì)象
cond = threading.Condition()
def consumer():
with cond:
print("Consumer: Waiting for condition")
cond.wait()
print("Consumer: Condition met")
def producer():
with cond:
print("Producer: Changing the condition")
# 發(fā)信號(hào)通知等待的線程
cond.notifyAll()
t1 = threading.Thread(target=consumer)
t2 = threading.Thread(target=producer)
t1.start()
t2.start()
在這個(gè)例子中,消費(fèi)者線程首先啟動(dòng)并等待條件達(dá)成。生產(chǎn)者線程改變條件,并通過(guò)調(diào)用cond.notifyAll()喚醒消費(fèi)者線程。
2 Python 多進(jìn)程
2.1 創(chuàng)建多進(jìn)程
Python要進(jìn)行多進(jìn)程操作,需要用到muiltprocessing庫(kù),其中的Process類(lèi)跟threading模塊的Thread類(lèi)很相似。
- 方法1:直接使用Process, 代碼如下:
from multiprocessing import Process
def show(name):
print("Process name is " + name)
if __name__ == "__main__":
proc = Process(target=show, args=('subprocess',))
proc.start()
proc.join()
- 方法2:繼承Process來(lái)自定義進(jìn)程類(lèi),重寫(xiě)run方法, 代碼如下:
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self, name):
super(MyProcess, self).__init__()
self.name = name
def run(self):
print('process name :' + str(self.name))
time.sleep(1)
if __name__ == '__main__':
for i in range(3):
p = MyProcess(i)
p.start()
for i in range(3):
p.join()
2.2 多進(jìn)程通信
進(jìn)程之間不共享數(shù)據(jù)的。如果進(jìn)程之間需要進(jìn)行通信,則要用到Queue模塊或者Pipe模塊來(lái)實(shí)現(xiàn)。
2.2.1 Queue
Queue是多進(jìn)程安全的隊(duì)列,可以實(shí)現(xiàn)多進(jìn)程之間的數(shù)據(jù)傳遞。它主要有兩個(gè)函數(shù)put和get。
put() 用以插入數(shù)據(jù)到隊(duì)列中,put還有兩個(gè)可選參數(shù):blocked 和timeout。如果blocked為 True(默認(rèn)值),并且timeout為正值,該方法會(huì)阻塞timeout指定的時(shí)間,直到該隊(duì)列有剩余的空間。如果超時(shí),會(huì)拋出 Queue.Full異常。如果blocked為False,但該Queue已滿,會(huì)立即拋出Queue.Full異常。
get()可以從隊(duì)列讀取并且刪除一個(gè)元素。同樣get有兩個(gè)可選參數(shù):blocked和timeout。如果blocked為T(mén)rue(默認(rèn)值),并且 timeout為正值,那么在等待時(shí)間內(nèi)沒(méi)有取到任何元素,會(huì)拋出Queue.Empty異常。如果blocked為False,有兩種情況存在,如果Queue有一個(gè)值可用,則立即返回該值,否則,如果隊(duì)列為空,則立即拋出Queue.Empty異常。
具體用法如下:
from multiprocessing import Process, Queue
def put(queue):
queue.put('Queue 用法')
if __name__ == '__main__':
queue = Queue()
pro = Process(target=put, args=(queue,))
pro.start()
print(queue.get())
pro.join()
2.2.2 Pipe
Pipe的本質(zhì)是進(jìn)程之間的用管道數(shù)據(jù)傳遞,而不是數(shù)據(jù)共享,這和socket有點(diǎn)像。pipe() 返回兩個(gè)連接對(duì)象分別表示管道的兩端,每端都有send()和recv()函數(shù)。如果兩個(gè)進(jìn)程試圖在同一時(shí)間的同一端進(jìn)行讀取和寫(xiě)入那么,這可能會(huì)損壞管道中的數(shù)據(jù),具體用法如下:
from multiprocessing import Process, Pipe
def show(conn):
conn.send('Pipe 用法')
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
pro = Process(target=show, args=(child_conn,))
pro.start()
print(parent_conn.recv())
pro.join()
2.3 進(jìn)程池
創(chuàng)建多個(gè)進(jìn)程,我們不用傻傻地一個(gè)個(gè)去創(chuàng)建。我們可以使用Pool模塊來(lái)搞定。Pool 常用的方法如下:
具體用法見(jiàn)示例代碼:
#coding: utf-8
import multiprocessing
import time
def func(msg):
print("msg:", msg)
time.sleep(3)
print("end")
if __name__ == "__main__":
# 維持執(zhí)行的進(jìn)程總數(shù)為processes,當(dāng)一個(gè)進(jìn)程執(zhí)行完畢后會(huì)添加新的進(jìn)程進(jìn)去
pool = multiprocessing.Pool(processes = 3)
for i in range(5):
msg = "hello %d" %(i)
# 非阻塞式,子進(jìn)程不影響主進(jìn)程的執(zhí)行,會(huì)直接運(yùn)行到 pool.join()
pool.apply_async(func, (msg, ))
# 阻塞式,先執(zhí)行完子進(jìn)程,再執(zhí)行主進(jìn)程
# pool.apply(func, (msg, ))
print("Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~")
# 調(diào)用join之前,先調(diào)用close函數(shù),否則會(huì)出錯(cuò)。
pool.close()
# 執(zhí)行完close后不會(huì)有新的進(jìn)程加入到pool,join函數(shù)等待所有子進(jìn)程結(jié)束
pool.join()
print("Sub-process(es) done.")
如上,進(jìn)程池Pool被創(chuàng)建出來(lái)后,即使實(shí)際需要?jiǎng)?chuàng)建的進(jìn)程數(shù)遠(yuǎn)遠(yuǎn)大于進(jìn)程池的最大上限,p.apply_async(test)代碼依舊會(huì)不停的執(zhí)行,并不會(huì)停下等待;相當(dāng)于向進(jìn)程池提交了10個(gè)請(qǐng)求,會(huì)被放到一個(gè)隊(duì)列中;
當(dāng)執(zhí)行完p1 = Pool(5)這條代碼后,5條進(jìn)程已經(jīng)被創(chuàng)建出來(lái)了,只是還沒(méi)有為他們各自分配任務(wù),也就是說(shuō),無(wú)論有多少任務(wù),實(shí)際的進(jìn)程數(shù)只有5條,計(jì)算機(jī)每次最多5條進(jìn)程并行。
當(dāng)Pool中有進(jìn)程任務(wù)執(zhí)行完畢后,這條進(jìn)程資源會(huì)被釋放,pool會(huì)按先進(jìn)先出的原則取出一個(gè)新的請(qǐng)求給空閑的進(jìn)程繼續(xù)執(zhí)行;
當(dāng)Pool所有的進(jìn)程任務(wù)完成后,會(huì)產(chǎn)生5個(gè)僵尸進(jìn)程,如果主線程不結(jié)束,系統(tǒng)不會(huì)自動(dòng)回收資源,需要調(diào)用join函數(shù)去回收。
join函數(shù)是主進(jìn)程等待子進(jìn)程結(jié)束回收系統(tǒng)資源的,如果沒(méi)有join,主程序退出后不管子進(jìn)程有沒(méi)有結(jié)束都會(huì)被強(qiáng)制殺死;
創(chuàng)建Pool池時(shí),如果不指定進(jìn)程最大數(shù)量,默認(rèn)創(chuàng)建的進(jìn)程數(shù)為系統(tǒng)的內(nèi)核數(shù)量.
3 選擇多線程還是多進(jìn)程?
在這個(gè)問(wèn)題上,首先要看下你的程序是屬于哪種類(lèi)型的。一般分為兩種:CPU密集型和I/O密集型。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-454957.html
- CPU 密集型:程序比較偏重于計(jì)算,需要經(jīng)常使用CPU來(lái)運(yùn)算。例如科學(xué)計(jì)算的程序,機(jī)器學(xué)習(xí)的程序等。
- I/O 密集型:顧名思義就是程序需要頻繁進(jìn)行輸入輸出操作。爬蟲(chóng)程序就是典型的I/O密集型程序。
如果程序是屬于CPU密集型,建議使用多進(jìn)程。而多線程就更適合應(yīng)用于I/O密集型程序。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-454957.html
到了這里,關(guān)于python多進(jìn)程與多線程的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!