Python并發(fā):多線程與多進(jìn)程的詳解
本篇概要
1.線程與多線程
2.進(jìn)程與多進(jìn)程
3.多線程并發(fā)下載圖片
4.多進(jìn)程并發(fā)提高數(shù)字運(yùn)算
關(guān)于并發(fā)
在計(jì)算機(jī)編程領(lǐng)域,并發(fā)編程是一個(gè)很常見(jiàn)的名詞和功能了,其實(shí)并發(fā)這個(gè)理念,最初是源于鐵路和電報(bào)的早期工作。比如在同一個(gè)鐵路系統(tǒng)上如何安排多列火車,保證每列火車的運(yùn)行都不會(huì)發(fā)生沖突。
后來(lái)在20世紀(jì)60年代,學(xué)術(shù)界對(duì)計(jì)算機(jī)的并行計(jì)算開(kāi)始進(jìn)行研究,再后來(lái),操作系統(tǒng)能夠進(jìn)行并發(fā)的處理任務(wù),編程語(yǔ)言能夠?yàn)槌绦驅(qū)崿F(xiàn)并發(fā)的功能。
線程與多線程
什么是線程
一個(gè)線程可以看成是一個(gè)有序的指令流(完成特定任務(wù)的指令),并且可以通過(guò)操作系統(tǒng)來(lái)調(diào)度這些指令流。
線程通常位于進(jìn)程程里面,由一個(gè)程序計(jì)數(shù)器、一個(gè)堆棧和一組寄存器以及一個(gè)標(biāo)識(shí)符組成。這些線程是處理器可以分配時(shí)間的最小執(zhí)行單元。
線程之間是可以共享內(nèi)存并且互相通信的。但是當(dāng)兩個(gè)線程之間開(kāi)始共享內(nèi)存,就無(wú)法保證線程執(zhí)行的順序,這可能導(dǎo)致程序錯(cuò)誤,或者產(chǎn)生錯(cuò)誤的結(jié)果。這個(gè)問(wèn)題我們?nèi)蘸髸?huì)專門提及。
下面這個(gè)圖片展示了多個(gè)線程在多個(gè)CPU中的存在方式:

線程的類型
在一個(gè)典型的操作系統(tǒng)里面,一般會(huì)有兩種類型的線程:
1.用戶級(jí)線程:我們能夠創(chuàng)建、運(yùn)行的線程;
2.內(nèi)核級(jí)線程:操作系統(tǒng)運(yùn)行的低級(jí)別線程;
Python工作在用戶級(jí)線程上,我們介紹的內(nèi)容也主要是在用戶級(jí)的線程上運(yùn)行的。
什么是多線程
現(xiàn)在的CPU基本上都是多線程的CPU,比如我們隨意從京東上找一個(gè)Inter的酷睿i5處理器,看看它的產(chǎn)品規(guī)格:

這些CPU能夠同時(shí)運(yùn)行多個(gè)線程來(lái)處理任務(wù),其實(shí)從本質(zhì)上來(lái)說(shuō),這些CPU是利用一個(gè)能夠在多個(gè)線程之間快速切換的單個(gè)內(nèi)核來(lái)完成多線程的運(yùn)行的,切換線程的速度足夠快,所以我們并不會(huì)感覺(jué)到。但實(shí)質(zhì)上,它們并不是同時(shí)運(yùn)行的。
為了形象的理解多線程,我們來(lái)回憶一個(gè)場(chǎng)景。
在大學(xué)時(shí)代,期末的時(shí)候,有些科目的老師為了不為難大家,把考試設(shè)為開(kāi)卷考試,不知道大家面對(duì)開(kāi)卷考試的時(shí)候,做題的順序是怎樣的?
在單線程的工作模式下,我們從選擇題到填空題到簡(jiǎn)答題再到分析題,一個(gè)一個(gè)按順序的寫(xiě)。
遇到一個(gè)特別難的題目,我們就要翻書(shū)翻資料了,當(dāng)然既然是開(kāi)卷考試,有些題目的答案就不可能直接出現(xiàn)在教科書(shū)中,那么我們就要花費(fèi)更多的時(shí)間來(lái)找答案,直到考試結(jié)束,因?yàn)槟硞€(gè)難題耗費(fèi)的翻書(shū)時(shí)間太多,導(dǎo)致后面一些簡(jiǎn)單的題目也沒(méi)用做,嗯,開(kāi)卷都寫(xiě)不完試卷,掛科名額就給你了。
而在多線程的工作模式下,我們也是按順序?qū)?,但是遇到難題時(shí),我們會(huì)稍微從書(shū)中找找答案,如果沒(méi)找到,就先做下面的題目,把會(huì)做的題目做好,做好了容易的題目,再回到那個(gè)難題上,仔細(xì)從書(shū)中的蛛絲馬跡中找答案。
在這個(gè)例子里面,我們只是一個(gè)人來(lái)完成,如果想要更快地完成考試,就得跟其他同學(xué)通力合作和分工了。
讓我們看看線程的一些優(yōu)點(diǎn):
1.多線程能夠有效提升I/O阻塞型程序的效率;
2.與進(jìn)程相比,占用的系統(tǒng)資源少;
3.線程間能夠共享資源,方便進(jìn)行通信;
線程還有一些缺點(diǎn):
1.Python中有全局解釋器鎖(GIL)的限制;
2.雖然線程之間能夠進(jìn)行通信,但是容易導(dǎo)致程序結(jié)果出錯(cuò),使用的時(shí)候必須小心;
3.在多線程之間切換的計(jì)算代價(jià)高,會(huì)導(dǎo)致程序的整體性能下降。
進(jìn)程與多進(jìn)程
進(jìn)程在本質(zhì)上與線程非常相似,進(jìn)程幾乎可以完成線程能夠完成的任何事情。
按照上面開(kāi)卷考試的例子,如果我們和室友組成一個(gè)小團(tuán)伙,那么我們就有四個(gè)CPU(4個(gè)人),四個(gè)人分別寫(xiě)和找不同的答案,這樣考試的效率會(huì)提高很多。
一個(gè)進(jìn)程里面,包含一個(gè)主線程,還可以生成很多子線程,每個(gè)線程都包含自己的寄存器組合堆棧。如果有需要的話,可以將它們組成多線程。
下面是單線程單進(jìn)程和多線程單進(jìn)程的示例:

進(jìn)程的特性
一個(gè)進(jìn)程通常包含以下的內(nèi)容:
1.進(jìn)程ID,進(jìn)程組ID,用戶ID,組ID
2.環(huán)境
3.工作目錄
4.程序指令
5.寄存器
6.堆棧
7.文件描述
8.進(jìn)程間通信工具
9.等等……
進(jìn)程有以下優(yōu)點(diǎn):
1.更好地利用多核處理器;
2.在處理CPU密集型任務(wù)時(shí)比多線程要好;
3.可以通過(guò)多進(jìn)程來(lái)避免全局解釋器鎖(GIL)的局限;
4.崩潰的進(jìn)程不會(huì)導(dǎo)致整個(gè)程序的崩潰;
同時(shí),還有以下缺點(diǎn):
1.進(jìn)程之間沒(méi)有共享資源;
2.進(jìn)程需要消耗更多的內(nèi)存;
多進(jìn)程
在Python中我們可以使用多線程或者多進(jìn)程的方式來(lái)運(yùn)行我們的代碼以改進(jìn)傳統(tǒng)的單線程方式的性能。
在單核的CPU上可以使用多線程提高處理能力,但是在現(xiàn)在的計(jì)算機(jī)CPU中,多核處理器早已普及,為了有效的利用機(jī)器的資源,我們有必要使用多進(jìn)程來(lái)發(fā)揮機(jī)器的價(jià)值。
一個(gè)CPU內(nèi)核將任務(wù)分配給其他CPU:

通過(guò)Python的進(jìn)程處理模塊multiprocessing,我們可以有效的利用機(jī)器上所有的處理器,這有助于我們?cè)谔幚鞢PU密集型任務(wù)時(shí)獲得更高的性能。
使用multiprocessing模塊,查看我們機(jī)器上的CPU核心數(shù)量:

結(jié)果返回一個(gè)數(shù)字,為CPU核心數(shù)。
多進(jìn)程不僅能夠提高我們的計(jì)算機(jī)的利用率,還能夠避免全局解釋器鎖的限制,一個(gè)潛在的缺點(diǎn)是多進(jìn)程間不能進(jìn)行共享和通信(可以通過(guò)其他手段實(shí)現(xiàn)),但是這個(gè)缺點(diǎn)同時(shí)也使多進(jìn)程更加容易使用和避免出現(xiàn)崩潰。
Python的局限性
在文章的前面,我們談到了在Python中存在的全局解釋器鎖GIL的局限性。那GIL到底是個(gè)什么東西?
GIL本質(zhì)上是一個(gè)互斥鎖,它可以防止多個(gè)線程同時(shí)執(zhí)行Python代碼。 它是一個(gè)只能由一個(gè)線程保持的鎖,如果你想要一個(gè)線程去執(zhí)行代碼,那么在它執(zhí)行代碼之前,首先必須獲得這個(gè)鎖。 這樣做的一個(gè)好處是,當(dāng)它被鎖定的時(shí)候,沒(méi)有別的進(jìn)程可以同時(shí)運(yùn)行代碼,一定程度上避免了線程間的沖突:

上面這個(gè)圖說(shuō)明了多個(gè)線程如何被GIL阻塞。每個(gè)線程必須等待獲取到GIL才能進(jìn)行下一步的運(yùn)行,然后再釋放GIL。線程之間使用隨機(jī)循環(huán)的方式,所以并不能控制和保證哪個(gè)線程會(huì)先得到GIL。
這樣的設(shè)計(jì)也是很多人詬病Python的地方。但是,這個(gè)設(shè)計(jì)確實(shí)是保證的多線程之間的內(nèi)存安全。
現(xiàn)在我們已經(jīng)了解了線程和進(jìn)程,以及Python的一些限制,現(xiàn)在是時(shí)候了解一下我們?nèi)绾卧趹?yīng)用程序中使用多線程多進(jìn)程,以提高程序的速度。
并發(fā)文件下載
毫無(wú)疑問(wèn)的,展現(xiàn)多線程優(yōu)點(diǎn)的一個(gè)例子就是使用多線程來(lái)下載多個(gè)圖片或者文件,由于I/O的阻塞性質(zhì),下載任務(wù)可能是多線程最佳的運(yùn)用場(chǎng)景了。
http://tool.bitefu.net/jiari/data/2017.txt是一個(gè)提供2017年所有節(jié)假日的文本文件:

我們?cè)L問(wèn)10次,獲得10次文本文件,然后保存在本地。
先看看一個(gè)普通的爬取:

我們引入了模塊urllib.request,然后創(chuàng)建了一個(gè)函數(shù)downloadImage()用于下載文件,創(chuàng)建了一個(gè)函數(shù)main()用于對(duì)下載函數(shù)進(jìn)行遍歷20次。

耗時(shí)4秒多。
下面看看使用多線程的:

程序的前部分大同小異,后面我們創(chuàng)建了一個(gè)threads列表,,然后遍歷10次,創(chuàng)建一個(gè)新的線程對(duì)象,將其添加到threads列表中,然后啟動(dòng)該線程。
最后,我們通過(guò)遍歷我們的threads列表來(lái)調(diào)用我們的線程,然后調(diào)用join()方法在每個(gè)線程上,這確保我們?cè)谙螺d完文件之前,不會(huì)執(zhí)行剩下的代碼。

運(yùn)行代碼,可以發(fā)現(xiàn)程序幾乎同時(shí)啟動(dòng)了10個(gè)下載任務(wù),然后在圖片下載完成后,再打印出來(lái)。
耗時(shí)0.1秒,效率提高很多。
但是需要注意的是,在網(wǎng)絡(luò)中進(jìn)行文件IO,還需要考慮網(wǎng)絡(luò)狀況和自身機(jī)器的影響,不同的網(wǎng)絡(luò)狀況下,完成的效率也不一樣。
并發(fā)數(shù)字運(yùn)算
I/O密集型的任務(wù)適合于多線程,而CPU密集型的任務(wù)則適合用多進(jìn)程。
在下面的例子里,我們將找出100萬(wàn)個(gè)20000到100000000之間隨機(jī)數(shù)的質(zhì)數(shù)。
順序運(yùn)算:


耗時(shí)18秒。
多進(jìn)程運(yùn)算:


耗時(shí)11秒。
我們分別按順序循環(huán)100萬(wàn)遍和使用多進(jìn)程的進(jìn)程池循環(huán)100萬(wàn)次,多進(jìn)程模式下速度提升了近7秒。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請(qǐng)查看下面相關(guān)鏈接
相關(guān)文章
Python中實(shí)現(xiàn)從目錄中過(guò)濾出指定文件類型的文件
這篇文章主要介紹了Python中實(shí)現(xiàn)從目錄中過(guò)濾出指定文件類型的文件,本文是一篇學(xué)筆記,實(shí)例相對(duì)簡(jiǎn)單,需要的朋友可以參考下2015-02-02
WxPython實(shí)現(xiàn)無(wú)邊框界面
這篇文章主要為大家詳細(xì)介紹了WxPython實(shí)現(xiàn)無(wú)邊框界面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11
python編程冒泡排序法實(shí)現(xiàn)動(dòng)圖排序示例解析
這篇文章主要介紹了python編程中如何使用冒泡排序法實(shí)現(xiàn)動(dòng)圖排序的示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-10-10
Python自定義聚合函數(shù)merge與transform區(qū)別詳解
這篇文章主要介紹了Python自定義聚合函數(shù)merge與transform區(qū)別詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05
python pip配置國(guó)內(nèi)鏡像源的方法(永久和臨時(shí))
在使用 pip 安裝 Python 模塊時(shí),默認(rèn)的國(guó)外鏡像源可能會(huì)導(dǎo)致下載速度緩慢甚至超時(shí),為了解決這個(gè)問(wèn)題,可以使用國(guó)內(nèi)的鏡像源來(lái)加速下載,以下是常用的國(guó)內(nèi)鏡像源以及臨時(shí)和永久的配置方法,需要的朋友可以參考下2025-04-04
keras實(shí)現(xiàn)圖像預(yù)處理并生成一個(gè)generator的案例
這篇文章主要介紹了keras實(shí)現(xiàn)圖像預(yù)處理并生成一個(gè)generator的案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-06-06
django框架之cookie/session的使用示例(小結(jié))
這篇文章主要介紹了django框架之cookie/session的使用示例(小結(jié)),詳細(xì)的介紹了cookie和session技術(shù)的接口獲取等問(wèn)題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
python修改注冊(cè)表終止360進(jìn)程實(shí)例
這篇文章主要介紹了python修改注冊(cè)表終止360進(jìn)程實(shí)例,是非常實(shí)用的進(jìn)程操作技巧,需要的朋友可以參考下2014-10-10

