中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

Python 中的多進(jìn)程與線程 每個(gè)數(shù)據(jù)科學(xué)家都需要知道

2020-03-03    來(lái)源:raincent

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

本文最初發(fā)布于 FLOYDHUB 博客,原作者:Sumit Ghosh

導(dǎo)讀:線程和進(jìn)程都是現(xiàn)在計(jì)算機(jī)領(lǐng)域比較時(shí)髦的用語(yǔ)。進(jìn)程 (Process) 是計(jì)算機(jī)中已運(yùn)行程序的實(shí)體。進(jìn)程本身不會(huì)運(yùn)行,是線程的容器。程序本身只是指令的集合,進(jìn)程才是程序(那些指令) 的真正運(yùn)行。若干進(jìn)程有可能與同一個(gè)程序相關(guān)系,且每個(gè)進(jìn)程皆可以同步(循序) 或不同步(平行) 的方式獨(dú)立運(yùn)行。進(jìn)程為現(xiàn)今分時(shí)系統(tǒng)的基本運(yùn)作單位。線程(thread),操作系統(tǒng)技術(shù)中的術(shù)語(yǔ),是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中,一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。今天,我們翻譯并分享 Sumit Ghosh 撰寫(xiě)的關(guān)于 Python 中的多進(jìn)程與線程的方方面面,這些內(nèi)容對(duì)于每個(gè)有志于成為數(shù)據(jù)科學(xué)家的從業(yè)者都是應(yīng)知必會(huì)的內(nèi)容。

 

 

每個(gè)數(shù)據(jù)科學(xué)項(xiàng)目遲早都會(huì)面臨一個(gè)不可避免的挑戰(zhàn):速度。使用更大的數(shù)據(jù)集會(huì)導(dǎo)致處理速度變慢,因此,最終不得不考慮優(yōu)化算法的運(yùn)行時(shí)間。正如大多數(shù)人所知道的,并行化就是這種優(yōu)化的必要步驟。Python 為并行化提供了兩個(gè)內(nèi)置庫(kù):multiprocessing(多進(jìn)程)和 threading(線程)。在本文中,我們將探討數(shù)據(jù)科學(xué)家如何在這兩者之間進(jìn)行選擇,以及在選擇時(shí)應(yīng)記住哪些因素。

并行計(jì)算與數(shù)據(jù)科學(xué)

眾所周知,數(shù)據(jù)科學(xué)是一門(mén)處理大量數(shù)據(jù),并從中提取有用見(jiàn)解的科學(xué)。通常情況下,我們?cè)跀?shù)據(jù)上執(zhí)行的操作很容易實(shí)現(xiàn)并行化,這意味著不同的處理可以在數(shù)據(jù)上一次運(yùn)行一個(gè)操作,然后在最后將結(jié)果組合起來(lái)以得到完整的結(jié)果。

為了更好地理解并行性,讓我們考慮一個(gè)真實(shí)世界的類比。假設(shè)你需要打掃家里的三個(gè)房間,你可以一個(gè)人包攬所有的事情:一個(gè)接一個(gè)地打掃房間,或者你也可以叫來(lái)兩個(gè)人幫助你,你們每個(gè)人只打掃一個(gè)房間。在后一種方法中,每個(gè)人都并行地處理整個(gè)任務(wù)的一部分,從而減少完成了任務(wù)所需的總時(shí)間,這就是并行性。

在 Python 中,可以通過(guò)兩種不同的方式實(shí)現(xiàn)并行處理:多進(jìn)程和線程。

多進(jìn)程和線程:理論

從根本上來(lái)說(shuō),多進(jìn)程和線程是實(shí)現(xiàn)并行計(jì)算的兩種方法,分別使用進(jìn)程和線程作為處理代理。要了解這些方法的工作原理,我們就必須弄清楚什么是進(jìn)程,什么是線程。

 

 

進(jìn)程

進(jìn)程是正在執(zhí)行的計(jì)算機(jī)程序的實(shí)例。每個(gè)進(jìn)程都有自己的內(nèi)存空間,用于存儲(chǔ)正在運(yùn)行的指令,以及需要存儲(chǔ)和訪問(wèn)用來(lái)執(zhí)行的任何數(shù)據(jù)。

線程

線程是進(jìn)程的組件,可以并行運(yùn)行。一個(gè)進(jìn)程可以有多個(gè)線程,它們共享相同的內(nèi)存空間,即父進(jìn)程的內(nèi)存空間。這意味著要執(zhí)行的代碼以及程序中聲明的所有變量,將由所有線程共享。

 

 

進(jìn)程和線程(圖:筆者與 Cburnett)

例如,讓我們考慮一下你的計(jì)算機(jī)上正在運(yùn)行的程序。你可能正在瀏覽器中閱讀本文,瀏覽器可能打開(kāi)了多個(gè)標(biāo)簽頁(yè)。你還可能同時(shí)通過(guò) Spotify 桌面應(yīng)用收聽(tīng)音樂(lè)。瀏覽器和 Spotify 應(yīng)用程序是不同的進(jìn)程,它們中的每一個(gè)都可以使用多個(gè)進(jìn)程或線程來(lái)實(shí)現(xiàn)并行性。瀏覽器中的不同標(biāo)簽頁(yè)可能在不同的線程中運(yùn)行。Spotify 可以在一個(gè)線程中播放音樂(lè),在另一個(gè)線程中從互聯(lián)網(wǎng)下載音樂(lè),并使用第三個(gè)線程來(lái)顯示 GUI。這就叫做多線程(multithreading)。多進(jìn)程(多即個(gè)進(jìn)程)也可以做到這一點(diǎn)。事實(shí)上,大多數(shù)像 Chrome 和 Firefox 這樣的現(xiàn)代瀏覽器使用的是多進(jìn)程而不是多線程來(lái)處理多個(gè)標(biāo)簽。

技術(shù)細(xì)節(jié)

一個(gè)進(jìn)程的所有線程都位于同一個(gè)內(nèi)存空間中,而進(jìn)程有各自獨(dú)立的內(nèi)存空間。

與進(jìn)程相比,線程更輕量級(jí),并且開(kāi)銷更低。生成進(jìn)程比生成線程要慢一些。

在線程之間共享對(duì)象更容易,因?yàn)樗鼈児蚕淼氖窍嗤膬?nèi)存空間。為了在進(jìn)程之間實(shí)現(xiàn)同樣的效果,我們必須使用一些類似 IPC(inter-process communication,進(jìn)程間通信)模型,通常是由操作系統(tǒng)提供的。

并行計(jì)算的陷阱

在程序中引入并行性并不總是一個(gè)正和博弈;有一些需要注意的陷阱。最重要的陷阱如下:

競(jìng)態(tài)條件: 正如我們已經(jīng)討論過(guò)的,線程具有共享的內(nèi)存空間,因此它們可以訪問(wèn)共享變量。當(dāng)多個(gè)線程試圖通過(guò)同時(shí)更改同一個(gè)變量時(shí),就會(huì)出現(xiàn)競(jìng)態(tài)條件。線程調(diào)度程序可以在線程之間任意切換,因此,我們無(wú)法知曉線程將試圖更改數(shù)據(jù)的順序。這可能導(dǎo)致兩個(gè)線程中的任何一個(gè)出現(xiàn)不正確的行為,特別是如果線程決定基于變量的值執(zhí)行某些操作時(shí)。為了防止這種情況的發(fā)生,可以在修改變量的代碼段周圍放置互斥鎖,這樣,一次只能有一個(gè)線程可以寫(xiě)入變量。

饑餓: 當(dāng)線程在更長(zhǎng)的時(shí)間內(nèi)被拒絕訪問(wèn)特定資源時(shí),就會(huì)發(fā)生饑餓,因此,整個(gè)程序速度就會(huì)變慢。這可能是涉及不良的線程調(diào)度算法的意外副作用。

死鎖: 過(guò)度使用互斥鎖也有一個(gè)缺點(diǎn),它可能會(huì)在程序中引入死鎖。死鎖是一個(gè)線程等待另一個(gè)線程將鎖釋放,但另一個(gè)線程需要一個(gè)資源來(lái)完成第一個(gè)線程所持有的鎖。這樣,兩個(gè)線程都會(huì)停止,程序也隨之停止。死鎖可以被看作是饑餓的一種極端情況。要避免這種情況,我們必須小心不要引入太多相互依賴的鎖。

活鎖: 活鎖是指線程在循環(huán)中繼續(xù)運(yùn)行,但沒(méi)有任何進(jìn)展。這也是由于涉及不良和互斥鎖的使用不當(dāng)造成的。

Python 中的多進(jìn)程和線程

全局解釋鎖

當(dāng)涉及到 Python 時(shí),有一些奇怪的地方需要記住。我們知道,線程共享相同的內(nèi)存空間,因此必須采取特殊的預(yù)防措施,以便兩個(gè)線程不會(huì)寫(xiě)入相同的內(nèi)存位置。CPython 解釋器使用一種名為 GIL 的機(jī)制或全局解釋鎖來(lái)處理這個(gè)問(wèn)題。

摘自 Python 的官方 wiki :

在 CPython 中,全局解釋鎖(Global Interpreter Lock,GIL)是一個(gè)互斥鎖,用來(lái)保護(hù)對(duì) Python 對(duì)象的訪問(wèn),防止多個(gè)線程同時(shí)執(zhí)行 Python 字節(jié)碼。這種鎖是必要的,主要是因?yàn)?CPython 的內(nèi)存管理不是線程安全的。

請(qǐng)查看這個(gè)幻燈片來(lái)了解 Python GIL 的詳細(xì)信息: Understanding the Python GIL

GIL 完成了它的工作,但是也付出了代價(jià)。它有效地序列化了解釋器級(jí)別的指令。它的工作原理如下:任何線程要執(zhí)行任何函數(shù),都必須獲取全局鎖。一次只能有一個(gè)線程可以獲得全局鎖,這意味著解釋器最終會(huì)串行地運(yùn)行指令。這種設(shè)計(jì)使內(nèi)存管理做到線程安全,但結(jié)果是,它根本不能利用多個(gè) CPU 內(nèi)核。在單核 CPU 中(這正是設(shè)計(jì)師在開(kāi)發(fā) CPython 時(shí)所考慮的),這并不是什么大問(wèn)題。但是,如果使用多核 CPU 的話,那么這個(gè)全局鎖最終將會(huì)成為一個(gè)瓶頸了。

如果你的程序在其他地方存在更嚴(yán)重的瓶頸,例如在網(wǎng)絡(luò)、IO、或者用戶交互方面,那么全局鎖這個(gè)瓶頸就變得無(wú)關(guān)緊要了。在這些情況下,線程化是一種完全有效的并行化方法。但對(duì)于計(jì)算密集型(CPU bound)的程序,線程化最終會(huì)使程序變慢。讓我們通過(guò)一些用例來(lái)探討這個(gè)問(wèn)題。

線程用例

GUI 程序始終使用線程來(lái)使應(yīng)用程序作出響應(yīng)。例如,在文本編輯程序中,一個(gè)線程負(fù)責(zé)記錄用戶輸入,另一個(gè)線程負(fù)責(zé)顯示文本,第三個(gè)線程負(fù)責(zé)拼寫(xiě)檢查,等等。在這里,程序必須等待用戶交互,這是最大的瓶頸。使用多進(jìn)程并不會(huì)使程序變得更快。

線程處理的另一個(gè)用例是 IO 密集型(IO bound)或網(wǎng)絡(luò)密集型的程序,比如 Web Scraper 。在這種情況下,多個(gè)線程可以負(fù)責(zé)并行抓取多個(gè) Web 網(wǎng)頁(yè)。線程必須從互聯(lián)網(wǎng)上下載網(wǎng)頁(yè),這將是最大的瓶頸,因此線程對(duì)于這種情況來(lái)說(shuō)是一個(gè)完美的解決方案。網(wǎng)絡(luò)密集型的 Web 服務(wù)器的工作方式類似:對(duì)于它們這種情況,多進(jìn)程并不比線程有任何優(yōu)勢(shì)。另一個(gè)相關(guān)的例子是 TensorFlow ,它使用線程池(thread pool)來(lái)并行地轉(zhuǎn)換數(shù)據(jù)。

多進(jìn)程的用例

在程序是計(jì)算密集型的,且不需要進(jìn)行任何 IO 或用戶交互的情況下,那么多進(jìn)程就比線程處理更為出色。例如,任何只處理數(shù)字的程序都將從多進(jìn)程中獲得巨大的加速;事實(shí)上,線程化處理可能會(huì)降低它的運(yùn)行速度。一個(gè)有趣的實(shí)際例子是 Pytorch Dataloader ,它使用多個(gè)子進(jìn)程將數(shù)據(jù)加載到 GPU 中。

在 Python 中的并行化

Python 為并行化方法提供了兩個(gè)同名的庫(kù): multiprocessing 和 threading。盡管它們之間存在根本的不同,但這兩個(gè)庫(kù)提供了非常相似的 API(從 Python 3.7 開(kāi)始)。讓我們看看它們的實(shí)際應(yīng)用。

import threading
import random
from functools import reduce

def func(number):
random_list = random.sample(range(1000000), number)
return reduce(lambda x, y: x*y, random_list)

number = 50000
thread1 = threading.Thread(target=func, args=(number,))
thread2 = threading.Thread(target=func, args=(number,))

thread1.start()
thread2.start()

thread1.join()
thread2.join()

你可以看到,我創(chuàng)建一個(gè)函數(shù) func,它創(chuàng)建了一個(gè)隨機(jī)數(shù)列表,然后按順序?qū)⑵渲械乃性叵喑。如果?xiàng)目數(shù)量足夠大,比如 5 萬(wàn)或 10 萬(wàn),這可能是一個(gè)相當(dāng)繁重的過(guò)程。

然后,我創(chuàng)建了兩個(gè)線程,它們將執(zhí)行相同的函數(shù)。線程對(duì)象有一個(gè)異步啟動(dòng)線程的 start 方法。如果我們想等待它們終止并返回,就必須調(diào)用 join 方法,這就是我們這段代碼所做的事情。

正如你所見(jiàn),在后臺(tái)將一個(gè)新線程轉(zhuǎn)化為一個(gè)任務(wù)的 API 非常簡(jiǎn)單。很棒的是,用于多進(jìn)程的 API 也幾乎完全相同;讓我們來(lái)看一下。

import multiprocessing
import random
from functools import reduce

def func(number):
random_list = random.sample(range(1000000), number)
return reduce(lambda x, y: x*y, random_list)

number = 50000
process1 = multiprocessing.Process(target=func, args=(number,))
process2 = multiprocessing.Process(target=func, args=(number,))

process1.start()
process2.start()

process1.join()
process2.join()

代碼就是這樣的,只需將 multiprocessing.Process 與 threading.Thread 進(jìn)行交換。 你使用多進(jìn)程實(shí)現(xiàn)了完全相同的程序。

很顯然,你可以用它做更多的事情,但這已不在本文的范疇之內(nèi),因此我們將不再贅述。如果有興趣了解更多相關(guān)信息,請(qǐng)查看 threading — Thread-based parallelism。

基準(zhǔn)

現(xiàn)在我們已經(jīng)了解了實(shí)現(xiàn)并行化的代碼是什么樣子的,讓我們回到性能問(wèn)題上來(lái)。正如我們之前所指出的,線程處理不適合計(jì)算密集型任務(wù)。在這種情況下,它最終會(huì)成為瓶頸。我們可以使用一些簡(jiǎn)單的基準(zhǔn)來(lái)驗(yàn)證這一點(diǎn)。

首先,讓我們看看上面所展示的代碼示例中線程和多進(jìn)程的比較。請(qǐng)記住,此任務(wù)不涉及任何類型的 IO,因此它是純計(jì)算密集型的任務(wù)。

 

 

讓我們來(lái)看看 IO 密集型的任務(wù)的類似基準(zhǔn)測(cè)試。例如,下面的函數(shù):

import requests

def func(number):
url = 'http://example.com/'
for i in range(number):
response = requests.get(url)
with open('example.com.txt', 'w') as output:
output.write(response.text)

 

這個(gè)函數(shù)的作用就是只獲取一個(gè)網(wǎng)頁(yè),并將其保存到本地文件中,如此循環(huán)多次。雖然沒(méi)有什么用,但是很直接,因此非常適合用來(lái)演示。讓我們看一下基準(zhǔn)測(cè)試。

 

 

從這兩張圖表中可以注意到以下幾點(diǎn):

在這兩種情況下,單個(gè)進(jìn)程比單個(gè)線程花費(fèi)更多的執(zhí)行時(shí)間。顯然,進(jìn)程的開(kāi)銷比線程更大。

對(duì)于計(jì)算密集型的任務(wù),多個(gè)進(jìn)程的性能要比多個(gè)線程的性能要好得多。然而,當(dāng)我們使用 8x 并行化時(shí),這種差異就變得不那么明顯了。由于我的筆記本的 CPU 是四核的,因此最多可以有四個(gè)進(jìn)程有效地使用多個(gè)內(nèi)核。因此,當(dāng)我使用更多進(jìn)程時(shí),它就不能很好地進(jìn)行擴(kuò)展。但是,它的性能仍然比線程要好很多,因?yàn)榫程根本就不能利用多核。

對(duì)于 IO 密集型的任務(wù),那么 CPU 就不是瓶頸了。因此,GIL 的常見(jiàn)限制在這里并不適用,而多進(jìn)程也沒(méi)有什么優(yōu)勢(shì)。不僅如此,線程的輕量級(jí)開(kāi)銷實(shí)際上使它們比多進(jìn)程更快,而且線程的性能始終優(yōu)于多進(jìn)程。

區(qū)別、優(yōu)點(diǎn)和缺點(diǎn)

線程在同一個(gè)內(nèi)存空間中運(yùn)行;進(jìn)程有獨(dú)立的內(nèi)存。

從之前的一點(diǎn)開(kāi)始:線程之間共享對(duì)象更容易,但問(wèn)題的另一面是,你必須采取額外的措施來(lái)進(jìn)行對(duì)象同步,確保兩個(gè)線程不會(huì)同時(shí)寫(xiě)入同一個(gè)對(duì)象,不會(huì)發(fā)生競(jìng)態(tài)條件。

由于對(duì)象同步增加了編程開(kāi)銷,多線程編程更容易出錯(cuò)。而另一方面,多進(jìn)程編程則很容易實(shí)現(xiàn)。

與進(jìn)程相比,線程的開(kāi)銷更低;生成進(jìn)程比線程花費(fèi)更多的時(shí)間。

由于 Python 中 GIL 的局限性,線程無(wú)法利用多個(gè) CPU 內(nèi)核實(shí)現(xiàn)真正的并行化。而多進(jìn)程則沒(méi)有任何這樣的限制。

進(jìn)程調(diào)度由 OS 處理,而線程調(diào)度由 Python 解釋器來(lái)完成。

子進(jìn)程是可中斷、可終止的,而子線程則不是。你必須等待線程終止或 join。

從所有這些討論中,我們可以得出以下結(jié)論:

線程應(yīng)該用于涉及 IO 或用戶交互的程序。

多進(jìn)程應(yīng)該用于計(jì)算密集型程序。

站在數(shù)據(jù)科學(xué)家的角度來(lái)看

典型的數(shù)據(jù)處理管道可以分為以下幾個(gè)步驟:

讀取?數(shù)據(jù)并存儲(chǔ)到主存儲(chǔ)器或 GPU 中。

使用 CPU 或 GPU 進(jìn)行計(jì)算。

將挖掘出的信息存儲(chǔ)在數(shù)據(jù)庫(kù)或磁盤(pán)中。

讓我們探索一下如何在這些任務(wù)中引入并行性,以便加快它們的運(yùn)行速度。

步驟 1 涉及從磁盤(pán)讀取數(shù)據(jù),因此顯然磁盤(pán) IO 將成為這一步驟的瓶頸。正如我們已經(jīng)討論過(guò)的,線程是并行化這種操作的最佳選擇。類似地,步驟 3 也是引入線程的理想候選步驟。

但是,步驟 2 包括了涉及 CPU 或 GPU 的計(jì)算。如果它是一個(gè)基于 CPU 的任務(wù),那么使用線程就沒(méi)有用;相反,我們必須進(jìn)行多進(jìn)程。只有這樣,我們才能充分利用 CPU 的多核并實(shí)現(xiàn)并行性。如果它是基于 GPU 的任務(wù),由于 GPU 已經(jīng)在硬件級(jí)別上實(shí)現(xiàn)了大規(guī)模并行化架構(gòu),使用正確的接口(庫(kù)和驅(qū)動(dòng)程序)與 GPU 交互應(yīng)該會(huì)解決其余的問(wèn)題。

 

 

現(xiàn)在你可能會(huì)想,“恐怕我的數(shù)據(jù)管道看起來(lái)有點(diǎn)不同啊;我有一些任務(wù)并不完全適合這個(gè)通用框架啊。”不過(guò),你應(yīng)該能夠觀察到此處用來(lái)決定線程和多進(jìn)程之間的關(guān)系。你應(yīng)該考慮的因素包括:

你的任務(wù)是否具有任何形式的 IO?

IO 是否為程序的瓶頸?

你的任務(wù)是否依賴于 CPU 的大量計(jì)算?

考慮到這些因素,再加上上面提到的要點(diǎn),你應(yīng)該能夠作出自己的決定了。另外,請(qǐng)記住,你不必在整個(gè)程序中,使用單一形式的并行化。你應(yīng)該為程序的不同部分使用其中一種或另一種形式的并行化,以適合該特定部分的為準(zhǔn)。

現(xiàn)在,我們來(lái)看一下數(shù)據(jù)科學(xué)家可能面臨的兩個(gè)示例場(chǎng)景,以及如何使用并行計(jì)算來(lái)加速它們。

場(chǎng)景:下載電子郵件

假設(shè)你想分析你自己的創(chuàng)業(yè)公司收件箱里的所有電子郵件,并了解趨勢(shì):誰(shuí)是發(fā)送頻率最高的發(fā)件人,在電子郵件中出現(xiàn)的最常見(jiàn)的關(guān)鍵詞是什么,一周中的哪一天或者一天中的哪個(gè)時(shí)段收到的電子郵件最多,等等。當(dāng)然,這個(gè)項(xiàng)目的第一步是將電子郵件下載到你的電腦上。

首先,讓我們按順序執(zhí)行,不使用任何并行化。下面是要使用的代碼,它應(yīng)該很容易理解。有一個(gè)函數(shù) download_emails,它將電子郵件的 ID 列表作為輸入,并按順序下載它們。這會(huì)將此函數(shù)與一次 100 封電子郵件列表的 ID 一起調(diào)用。

import imaplib
import time

IMAP_SERVER = 'imap.gmail.com'
USERNAME = 'username@gmail.com'
PASSWORD = 'password'

def download_emails(ids):
client = imaplib.IMAP4_SSL(IMAP_SERVER)
client.login(USERNAME, PASSWORD)
client.select()
for i in ids:
print(f'Downloading mail id: {i.decode()}')
_, data = client.fetch(i, '(RFC822)')
with open(f'emails/{i.decode()}.eml', 'wb') as f:
f.write(data 0 )
client.close()
print(f'Downloaded {len(ids)} mails!')

start = time.time()

client = imaplib.IMAP4_SSL(IMAP_SERVER)
client.login(USERNAME, PASSWORD)
client.select()
_, ids = client.search(None, 'ALL')
ids = ids[0].split()
ids = ids[:100]
client.close()

download_emails(ids)
print('Time:', time.time() - start)

Time taken :: 35.65300488471985 seconds.

現(xiàn)在,讓我們?cè)谶@個(gè)任務(wù)中引入一些并行化能力,以加快速度。在開(kāi)始編寫(xiě)代碼之前,我們必須在線程和多進(jìn)程之間作出決定。正如你到目前為止所了解的那樣,當(dāng)涉及到 IO 為瓶頸的任務(wù)時(shí),線程就是最佳選擇。手邊的任務(wù)顯然屬于這一類,因?yàn)樗峭ㄟ^(guò)互聯(lián)網(wǎng)訪問(wèn) IMAP 服務(wù)器。因此我們將使用 threading(線程)。

我們將要使用的大部分代碼與我們?cè)陧樞蚯闆r下使用的代碼是相同的。唯一的區(qū)別是,我們將 100 封電子郵件的 ID 列表拆分為 10 個(gè)較小的塊,每一塊包含 10 個(gè) ID,然后創(chuàng)建 10 個(gè)線程并使用不同的塊調(diào)用函數(shù) download_emails。我使用 Python 標(biāo)準(zhǔn)庫(kù)中的 concurrent.futures.ThreadPoolExecutor 類進(jìn)行線程化處理。

import imaplib
import time
from concurrent.futures import ThreadPoolExecutor

IMAP_SERVER = 'imap.gmail.com'
USERNAME = 'username@gmail.com'
PASSWORD = 'password'

def download_emails(ids):
client = imaplib.IMAP4_SSL(IMAP_SERVER)
client.login(USERNAME, PASSWORD)
client.select()
for i in ids:
print(f'Downloading mail id: {i.decode()}')
_, data = client.fetch(i, '(RFC822)')
with open(f'emails/{i.decode()}.eml', 'wb') as f:
f.write(data 0 )
client.close()
print(f'Downloaded {len(ids)} mails!')

start = time.time()

client = imaplib.IMAP4_SSL(IMAP_SERVER)
client.login(USERNAME, PASSWORD)
client.select()
_, ids = client.search(None, 'ALL')
ids = ids[0].split()
ids = ids[:100]
client.close()

number_of_chunks = 10
chunk_size = 10
executor = ThreadPoolExecutor(max_workers=number_of_chunks)
futures = []
for i in range(number_of_chunks):
chunk = ids[i*chunk_size:(i+1)*chunk_size]
futures.append(executor.submit(download_emails, chunk))

for future in concurrent.futures.as_completed(futures):
pass
print('Time:', time.time() - start)

Time taken :: 9.841094255447388 seconds.

正如你所看到的,線程化大大加快了執(zhí)行速度。

場(chǎng)景:使用 Scikit-Learn 進(jìn)行分類

假設(shè)你有一個(gè)分類問(wèn)題,想為此使用隨機(jī)森林(random forest)分類器。因?yàn)樗且环N標(biāo)準(zhǔn)的、眾所周知的機(jī)器學(xué)習(xí)方法,所以我們不打算“重新發(fā)明輪子 ”,只使用 sklearn.ensemble.RandomForestClassifier。

下面的代碼段用于演示目的。我使用輔助函數(shù) sklearn.datasets.make_classification 創(chuàng)建了一個(gè)分類數(shù)據(jù)集,然后在此基礎(chǔ)上訓(xùn)練了一個(gè) RandomForestClassifier 。此外,我正在對(duì)代碼中做核心工作的部分進(jìn)行計(jì)時(shí),以對(duì)模型進(jìn)行擬合。

from sklearn.ensemble import RandomForestClassifier
from sklearn import datasets
import time

X, y = datasets.make_classification(n_samples=10000, n_features=50, n_informative=20, n_classes=10)

start = time.time()
model = RandomForestClassifier(n_estimators=500)
model.fit(X, y)
print('Time:', time.time()-start)

Time taken :: 34.17733192443848 seconds.

現(xiàn)在,我們來(lái)看看如何減少這個(gè)算法的運(yùn)行時(shí)間。我們知道這個(gè)算法在一定程度上實(shí)行并行化,但什么樣的并行化才是合適的呢?它沒(méi)有任何 IO 瓶頸;相反,這是一項(xiàng)非常耗費(fèi) CPU 的任務(wù)。因此,多進(jìn)程將是合理的選擇。

幸運(yùn)的是, sklearn 已經(jīng)在這個(gè)算法中實(shí)現(xiàn)了多進(jìn)程,我們不必從頭開(kāi)始編寫(xiě)。正如你在下面的代碼中所看到的那樣,我們只需提供一個(gè)參數(shù) n_jobs ,即它應(yīng)該使用的進(jìn)程數(shù)量,來(lái)啟用多進(jìn)程。

from sklearn.ensemble import RandomForestClassifier
from sklearn import datasets
import time

X, y = datasets.make_classification(n_samples=10000, n_features=50, n_informative=20, n_classes=10)

start = time.time()
model = RandomForestClassifier(n_estimators=500, n_jobs=4)
model.fit(X, y)
print('Time:', time.time()-start)

Time taken :: 14.576200723648071 seconds.

正如預(yù)期的那樣,多進(jìn)程使其運(yùn)行速度提高了很多。

結(jié)論

大多數(shù)(如果不是所有的話)數(shù)據(jù)科學(xué)項(xiàng)目將會(huì)看到并行計(jì)算速度大幅提高。事實(shí)上,許多流行的數(shù)據(jù)科學(xué)庫(kù)已經(jīng)內(nèi)置了并行性,你只需啟用它即可。因此,在嘗試自己實(shí)現(xiàn)它之前,請(qǐng)先查看正在使用的庫(kù)的文檔,并檢查它是否支持并行性(順便說(shuō)一句,我強(qiáng)烈建議你查看 dask )。如果沒(méi)有的話,希望本文能夠幫助你自己來(lái)實(shí)現(xiàn)并行性。

作者介紹:

Sumit 是一名計(jì)算機(jī)愛(ài)好者,很小就開(kāi)始編程。目前正在德里印度理工學(xué)院(IIT Delhi)攻讀計(jì)算機(jī)科學(xué)碩士學(xué)位。除了編程之外,他還喜歡哲學(xué)、吉他、攝影和寫(xiě)作。

原文鏈接:

Multiprocessing vs. Threading in Python: What Every Data Scientist Needs to Know

標(biāo)簽: Python 數(shù)據(jù) 蒲Ъ

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:十個(gè)技巧,讓你成為數(shù)據(jù)分析中的“降維”專家

下一篇:大型數(shù)據(jù)庫(kù)支持面部識(shí)別抓取,隱私何處安放?