Python多線程與多處理之間的區(qū)別詳解
一、說(shuō)明
在本文中,我們將學(xué)習(xí) Python 中多線程和多處理的內(nèi)容、原因和方式。在我們深入研究代碼之前,讓我們了解這些術(shù)語(yǔ)的含義。
二、基本術(shù)語(yǔ)和概念
程序是一個(gè)可執(zhí)行文件,它由一組執(zhí)行某些任務(wù)的指令組成,通常存儲(chǔ)在計(jì)算機(jī)的磁盤上。
進(jìn)程就是我們所說(shuō)的程序,它已與運(yùn)行所需的所有資源一起加載到內(nèi)存中。它有自己的內(nèi)存空間。
線程是進(jìn)程中的執(zhí)行單元。一個(gè)進(jìn)程可以有多個(gè)線程作為其一部分運(yùn)行,其中每個(gè)線程使用進(jìn)程的內(nèi)存空間并與其他線程共享。
多線程是一種技術(shù),其中進(jìn)程生成多個(gè)線程以執(zhí)行不同的任務(wù),大約在同一時(shí)間,一個(gè)接一個(gè)。這給你一種錯(cuò)覺,即線程是并行運(yùn)行的,但實(shí)際上它們是以并發(fā)方式運(yùn)行的。在 Python 中,全局解釋器鎖 (GIL) 阻止線程同時(shí)運(yùn)行。
多處理是一種實(shí)現(xiàn)最真實(shí)形式的并行性的技術(shù)。多個(gè)進(jìn)程跨多個(gè) CPU 內(nèi)核運(yùn)行,這些內(nèi)核之間不共享資源。每個(gè)進(jìn)程可以在自己的內(nèi)存空間中運(yùn)行許多線程。在 Python 中,每個(gè)進(jìn)程都有自己的 Python 解釋器實(shí)例,負(fù)責(zé)執(zhí)行指令。
現(xiàn)在,讓我們進(jìn)入程序,我們嘗試以六種不同的方式執(zhí)行兩種不同類型的函數(shù):IO 綁定和 CPU 綁定。在 IO 綁定函數(shù)中,我們要求 CPU 閑置并打發(fā)時(shí)間,而在 CPU 綁定函數(shù)中,CPU 將忙于產(chǎn)生一些數(shù)字。
要求:
- 一臺(tái) Windows 計(jì)算機(jī)(我的機(jī)器有 6 個(gè)內(nèi)核)。
- 已安裝 Python 3.x。
- 任何用于編寫 Python 程序的文本編輯器/IDE(我在這里使用 Sublime Text)。
注意:以下是我們程序的結(jié)構(gòu),它將在所有六個(gè)部分中通用。在提到它的地方 # YOUR CODE SNIPPET HERE,將其替換為每個(gè)部分的代碼片段。
import time, os from threading import Thread, current_thread from multiprocessing import Process, current_process COUNT = 200000000 SLEEP = 10 def io_bound(sec): pid = os.getpid() threadName = current_thread().name processName = current_process().name print(f"{pid} * {processName} * {threadName} \ ---> Start sleeping...") time.sleep(sec) print(f"{pid} * {processName} * {threadName} \ ---> Finished sleeping...") def cpu_bound(n): pid = os.getpid() threadName = current_thread().name processName = current_process().name print(f"{pid} * {processName} * {threadName} \ ---> Start counting...") while n>0: n -= 1 print(f"{pid} * {processName} * {threadName} \ ---> Finished counting...") if __name__=="__main__": start = time.time() # YOUR CODE SNIPPET HERE end = time.time() print('Time taken in seconds -', end - start)
三、進(jìn)程對(duì)CPU綁定
第 1 部分:一個(gè)接一個(gè)地運(yùn)行 IO 綁定任務(wù)兩次…
# Code snippet for Part 1 io_bound(SLEEP) io_bound(SLEEP)
在這里,我們要求 CPU 執(zhí)行函數(shù) io_bound(),該函數(shù)將整數(shù)(此處為 10)作為參數(shù),并要求 CPU 休眠幾秒鐘。此執(zhí)行總共需要 20 秒,因?yàn)槊總€(gè)函數(shù)執(zhí)行需要 10 秒才能完成。請(qǐng)注意,它是同一個(gè) MainProcess 使用其默認(rèn)線程 MainThread 一個(gè)接一個(gè)地調(diào)用我們的函數(shù)兩次。
第 2 部分:使用線程運(yùn)行受 IO 綁定的任務(wù)…
# Code snippet for Part 2 t1 = Thread(target = io_bound, args =(SLEEP, )) t2 = Thread(target = io_bound, args =(SLEEP, )) t1.start() t2.start() t1.join() t2.join()
在這里,讓我們使用 Python 中的線程來(lái)加快函數(shù)的執(zhí)行速度。線程 Thread-1 和 Thread-2 由我們的 MainProcess 啟動(dòng),每個(gè)線程幾乎同時(shí)調(diào)用我們的函數(shù)。兩個(gè)線程同時(shí)完成休眠 10 秒的工作。這大大縮短了整個(gè)程序的總執(zhí)行時(shí)間,減少了 50%。因此,多線程是執(zhí)行任務(wù)的首選解決方案,其中 CPU 的空閑時(shí)間可用于執(zhí)行其他任務(wù)。因此,通過利用等待時(shí)間來(lái)節(jié)省時(shí)間。
第 3 部分:一個(gè)接一個(gè)地運(yùn)行兩次 CPU 密集型任務(wù)…
# Code snippet for Part 3 cpu_bound(COUNT) cpu_bound(COUNT)
在這里,我們將調(diào)用我們的函數(shù) cpu_bound(),它將一個(gè)大數(shù)字(此處為 200000000)作為參數(shù),并在每一步將其遞減,直到它為零。我們的 CPU 被要求在每次函數(shù)調(diào)用時(shí)進(jìn)行倒計(jì)時(shí),這大約需要 12 秒(這個(gè)數(shù)字可能因您的計(jì)算機(jī)而異)。因此,整個(gè)程序的執(zhí)行花了我大約 26 秒才能完成。請(qǐng)注意,MainProcess 再次在其默認(rèn)線程 MainThread 中一個(gè)接一個(gè)地調(diào)用該函數(shù)兩次。
第 4 部分:線程可以加快我們受 CPU 限制的任務(wù)嗎?
# Code snippet for Part 4 t1 = Thread(target = cpu_bound, args =(COUNT, )) t2 = Thread(target = cpu_bound, args =(COUNT, )) t1.start() t2.start() t1.join() t2.join()
好的,我們剛剛證明了線程對(duì)于多個(gè) IO 綁定任務(wù)的效果非常好。讓我們使用相同的方法來(lái)執(zhí)行 CPU 密集型任務(wù)。好吧,它最初確實(shí)同時(shí)啟動(dòng)了我們的線程,但最終,我們看到整個(gè)程序執(zhí)行花費(fèi)了大約 40 秒!剛剛發(fā)生了什么?這是因?yàn)楫?dāng) Thread-1 啟動(dòng)時(shí),它獲得了全局解釋器鎖 (GIL),從而阻止 Thread-2 使用 CPU。因此,Thread-2 必須等待 Thread-1 完成其任務(wù)并釋放鎖,以便它可以獲取鎖并執(zhí)行其任務(wù)。這種鎖的獲取和釋放增加了總執(zhí)行時(shí)間的開銷。因此,我們可以肯定地說(shuō),對(duì)于需要 CPU 處理某事的任務(wù)來(lái)說(shuō),線程并不是一個(gè)理想的解決方案。
第 5 部分:那么,將任務(wù)拆分為單獨(dú)的流程是否有效?
# Code snippet for Part 5 p1 = Process(target = cpu_bound, args =(COUNT, )) p2 = Process(target = cpu_bound, args =(COUNT, )) p1.start() p2.start() p1.join() p2.join()
讓我們切入正題。多處理就是答案。在這里,MainProcess 啟動(dòng)了兩個(gè)子進(jìn)程,它們具有不同的 PID,每個(gè)子進(jìn)程都負(fù)責(zé)將數(shù)字減少到零。每個(gè)進(jìn)程并行運(yùn)行,使用單獨(dú)的 CPU 內(nèi)核和它自己的 Python 解釋器實(shí)例,因此整個(gè)程序執(zhí)行只需 12 秒。請(qǐng)注意,輸出可能以無(wú)序方式打印,因?yàn)檫M(jìn)程彼此獨(dú)立。每個(gè)進(jìn)程都在其自己的默認(rèn)線程 MainThread 中執(zhí)行函數(shù)。在程序執(zhí)行期間打開任務(wù)管理器。您可以看到 Python 解釋器的 3 個(gè)實(shí)例,MainProcess、Process-1 和 Process-2 各一個(gè)。您還可以看到,在程序執(zhí)行期間,兩個(gè)子進(jìn)程的功耗為“非常高”,因?yàn)樗鼈冋趫?zhí)行的任務(wù)實(shí)際上正在對(duì)它們自己的 CPU 內(nèi)核造成影響,如 CPU 性能圖中的峰值所示。
第 6 部分:我們對(duì) IO 綁定任務(wù)使用多處理…
# Code snippet for Part 6 p1 = Process(target = io_bound, args =(SLEEP, )) p2 = Process(target = io_bound, args =(SLEEP, )) p1.start() p2.start() p1.join() p2.join()
現(xiàn)在我們已經(jīng)對(duì)多處理幫助我們實(shí)現(xiàn)并行性有了大致的了解,我們將嘗試使用這種技術(shù)來(lái)運(yùn)行我們的 IO 綁定任務(wù)。我們確實(shí)觀察到結(jié)果是非凡的,就像在多線程的情況下一樣。由于進(jìn)程 1 和進(jìn)程 2 正在執(zhí)行要求自己的 CPU 內(nèi)核閑置幾秒鐘的任務(wù),因此我們沒有發(fā)現(xiàn)高功耗。但是,進(jìn)程的創(chuàng)建本身就是一項(xiàng) CPU 繁重的任務(wù),并且比創(chuàng)建線程需要更多的時(shí)間。此外,進(jìn)程需要的資源比線程多。因此,最好將多處理作為 IO 綁定任務(wù)的第二個(gè)選項(xiàng),多線程是第一個(gè)選項(xiàng)。
嗯,那是一段相當(dāng)長(zhǎng)的旅程。我們看到了執(zhí)行一項(xiàng)任務(wù)的六種不同方法,大約需要 10 秒,具體取決于任務(wù)對(duì) CPU 的影響是輕還是重。
四、結(jié)論
底線:IO 綁定任務(wù)的多線程處理。CPU密集型任務(wù)的多處理。
Python 中的多線程 | Python 中的多處理 |
---|---|
它實(shí)現(xiàn)了并發(fā)性。 | 它實(shí)現(xiàn)了并行性。 |
在并行計(jì)算的情況下,Python 不支持多線程。 | Python 在并行計(jì)算的情況下支持多處理。 |
在多線程中,單個(gè)進(jìn)程同時(shí)生成多個(gè)線程。 | 在多處理中,多個(gè)線程同時(shí)跨多個(gè)內(nèi)核運(yùn)行。 |
無(wú)法對(duì)多線程進(jìn)行分類。 | 多處理可以分為對(duì)稱或非對(duì)稱。 |
以上就是Python多線程與多處理之間的區(qū)別詳解的詳細(xì)內(nèi)容,更多關(guān)于Python多線程與多處理區(qū)別的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用Django開發(fā)簡(jiǎn)單接口實(shí)現(xiàn)文章增刪改查
這篇文章主要介紹了使用Django開發(fā)簡(jiǎn)單接口實(shí)現(xiàn)文章增刪改查,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05python將字符串轉(zhuǎn)換成json的方法小結(jié)
這篇文章主要介紹了python將字符串轉(zhuǎn)換成json的方法小結(jié),通過實(shí)例代碼給大家介紹將字符串型的數(shù)據(jù)轉(zhuǎn)換成dict類型遇到的問題,需要的朋友可以參考下2019-07-07Python實(shí)現(xiàn)的彩票機(jī)選器實(shí)例
這篇文章主要介紹了Python實(shí)現(xiàn)彩票機(jī)選器的方法,可以模擬彩票號(hào)碼的隨機(jī)生成功能,需要的朋友可以參考下2015-06-06Python中l(wèi)ambda表達(dá)式的使用詳解(完整通透版)
這篇文章主要介紹了Python中l(wèi)ambda表達(dá)式使用的相關(guān)資料,包括其基本語(yǔ)法、常見應(yīng)用場(chǎng)景(如排序、map、filter、reduce函數(shù)結(jié)合使用)以及如何在函數(shù)內(nèi)部或一次性使用,通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-12-12Python 讀取某個(gè)目錄下所有的文件實(shí)例
今天小編就為大家分享一篇Python 讀取某個(gè)目錄下所有的文件實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2018-06-06python實(shí)現(xiàn)類似ftp傳輸文件的網(wǎng)絡(luò)程序示例
這篇文章主要介紹了python實(shí)現(xiàn)類似ftp傳輸文件的網(wǎng)絡(luò)程序示例,需要的朋友可以參考下2014-04-04