Python并發(fā):多線程與多進(jìn)程的詳解
本篇概要
1.線程與多線程
2.進(jìn)程與多進(jìn)程
3.多線程并發(fā)下載圖片
4.多進(jìn)程并發(fā)提高數(shù)字運算
關(guān)于并發(fā)
在計算機(jī)編程領(lǐng)域,并發(fā)編程是一個很常見的名詞和功能了,其實并發(fā)這個理念,最初是源于鐵路和電報的早期工作。比如在同一個鐵路系統(tǒng)上如何安排多列火車,保證每列火車的運行都不會發(fā)生沖突。
后來在20世紀(jì)60年代,學(xué)術(shù)界對計算機(jī)的并行計算開始進(jìn)行研究,再后來,操作系統(tǒng)能夠進(jìn)行并發(fā)的處理任務(wù),編程語言能夠為程序?qū)崿F(xiàn)并發(fā)的功能。
線程與多線程
什么是線程
一個線程可以看成是一個有序的指令流(完成特定任務(wù)的指令),并且可以通過操作系統(tǒng)來調(diào)度這些指令流。
線程通常位于進(jìn)程程里面,由一個程序計數(shù)器、一個堆棧和一組寄存器以及一個標(biāo)識符組成。這些線程是處理器可以分配時間的最小執(zhí)行單元。
線程之間是可以共享內(nèi)存并且互相通信的。但是當(dāng)兩個線程之間開始共享內(nèi)存,就無法保證線程執(zhí)行的順序,這可能導(dǎo)致程序錯誤,或者產(chǎn)生錯誤的結(jié)果。這個問題我們?nèi)蘸髸iT提及。
下面這個圖片展示了多個線程在多個CPU中的存在方式:
線程的類型
在一個典型的操作系統(tǒng)里面,一般會有兩種類型的線程:
1.用戶級線程:我們能夠創(chuàng)建、運行的線程;
2.內(nèi)核級線程:操作系統(tǒng)運行的低級別線程;
Python工作在用戶級線程上,我們介紹的內(nèi)容也主要是在用戶級的線程上運行的。
什么是多線程
現(xiàn)在的CPU基本上都是多線程的CPU,比如我們隨意從京東上找一個Inter的酷睿i5處理器,看看它的產(chǎn)品規(guī)格:
這些CPU能夠同時運行多個線程來處理任務(wù),其實從本質(zhì)上來說,這些CPU是利用一個能夠在多個線程之間快速切換的單個內(nèi)核來完成多線程的運行的,切換線程的速度足夠快,所以我們并不會感覺到。但實質(zhì)上,它們并不是同時運行的。
為了形象的理解多線程,我們來回憶一個場景。
在大學(xué)時代,期末的時候,有些科目的老師為了不為難大家,把考試設(shè)為開卷考試,不知道大家面對開卷考試的時候,做題的順序是怎樣的?
在單線程的工作模式下,我們從選擇題到填空題到簡答題再到分析題,一個一個按順序的寫。
遇到一個特別難的題目,我們就要翻書翻資料了,當(dāng)然既然是開卷考試,有些題目的答案就不可能直接出現(xiàn)在教科書中,那么我們就要花費更多的時間來找答案,直到考試結(jié)束,因為某個難題耗費的翻書時間太多,導(dǎo)致后面一些簡單的題目也沒用做,嗯,開卷都寫不完試卷,掛科名額就給你了。
而在多線程的工作模式下,我們也是按順序?qū)?,但是遇到難題時,我們會稍微從書中找找答案,如果沒找到,就先做下面的題目,把會做的題目做好,做好了容易的題目,再回到那個難題上,仔細(xì)從書中的蛛絲馬跡中找答案。
在這個例子里面,我們只是一個人來完成,如果想要更快地完成考試,就得跟其他同學(xué)通力合作和分工了。
讓我們看看線程的一些優(yōu)點:
1.多線程能夠有效提升I/O阻塞型程序的效率;
2.與進(jìn)程相比,占用的系統(tǒng)資源少;
3.線程間能夠共享資源,方便進(jìn)行通信;
線程還有一些缺點:
1.Python中有全局解釋器鎖(GIL)的限制;
2.雖然線程之間能夠進(jìn)行通信,但是容易導(dǎo)致程序結(jié)果出錯,使用的時候必須小心;
3.在多線程之間切換的計算代價高,會導(dǎo)致程序的整體性能下降。
進(jìn)程與多進(jìn)程
進(jìn)程在本質(zhì)上與線程非常相似,進(jìn)程幾乎可以完成線程能夠完成的任何事情。
按照上面開卷考試的例子,如果我們和室友組成一個小團(tuán)伙,那么我們就有四個CPU(4個人),四個人分別寫和找不同的答案,這樣考試的效率會提高很多。
一個進(jìn)程里面,包含一個主線程,還可以生成很多子線程,每個線程都包含自己的寄存器組合堆棧。如果有需要的話,可以將它們組成多線程。
下面是單線程單進(jìn)程和多線程單進(jìn)程的示例:
進(jìn)程的特性
一個進(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)點:
1.更好地利用多核處理器;
2.在處理CPU密集型任務(wù)時比多線程要好;
3.可以通過多進(jìn)程來避免全局解釋器鎖(GIL)的局限;
4.崩潰的進(jìn)程不會導(dǎo)致整個程序的崩潰;
同時,還有以下缺點:
1.進(jìn)程之間沒有共享資源;
2.進(jìn)程需要消耗更多的內(nèi)存;
多進(jìn)程
在Python中我們可以使用多線程或者多進(jìn)程的方式來運行我們的代碼以改進(jìn)傳統(tǒng)的單線程方式的性能。
在單核的CPU上可以使用多線程提高處理能力,但是在現(xiàn)在的計算機(jī)CPU中,多核處理器早已普及,為了有效的利用機(jī)器的資源,我們有必要使用多進(jìn)程來發(fā)揮機(jī)器的價值。
一個CPU內(nèi)核將任務(wù)分配給其他CPU:
通過Python的進(jìn)程處理模塊multiprocessing,我們可以有效的利用機(jī)器上所有的處理器,這有助于我們在處理CPU密集型任務(wù)時獲得更高的性能。
使用multiprocessing模塊,查看我們機(jī)器上的CPU核心數(shù)量:
結(jié)果返回一個數(shù)字,為CPU核心數(shù)。
多進(jìn)程不僅能夠提高我們的計算機(jī)的利用率,還能夠避免全局解釋器鎖的限制,一個潛在的缺點是多進(jìn)程間不能進(jìn)行共享和通信(可以通過其他手段實現(xiàn)),但是這個缺點同時也使多進(jìn)程更加容易使用和避免出現(xiàn)崩潰。
Python的局限性
在文章的前面,我們談到了在Python中存在的全局解釋器鎖GIL的局限性。那GIL到底是個什么東西?
GIL本質(zhì)上是一個互斥鎖,它可以防止多個線程同時執(zhí)行Python代碼。 它是一個只能由一個線程保持的鎖,如果你想要一個線程去執(zhí)行代碼,那么在它執(zhí)行代碼之前,首先必須獲得這個鎖。 這樣做的一個好處是,當(dāng)它被鎖定的時候,沒有別的進(jìn)程可以同時運行代碼,一定程度上避免了線程間的沖突:
上面這個圖說明了多個線程如何被GIL阻塞。每個線程必須等待獲取到GIL才能進(jìn)行下一步的運行,然后再釋放GIL。線程之間使用隨機(jī)循環(huán)的方式,所以并不能控制和保證哪個線程會先得到GIL。
這樣的設(shè)計也是很多人詬病Python的地方。但是,這個設(shè)計確實是保證的多線程之間的內(nèi)存安全。
現(xiàn)在我們已經(jīng)了解了線程和進(jìn)程,以及Python的一些限制,現(xiàn)在是時候了解一下我們?nèi)绾卧趹?yīng)用程序中使用多線程多進(jìn)程,以提高程序的速度。
并發(fā)文件下載
毫無疑問的,展現(xiàn)多線程優(yōu)點的一個例子就是使用多線程來下載多個圖片或者文件,由于I/O的阻塞性質(zhì),下載任務(wù)可能是多線程最佳的運用場景了。
http://tool.bitefu.net/jiari/data/2017.txt是一個提供2017年所有節(jié)假日的文本文件:
我們訪問10次,獲得10次文本文件,然后保存在本地。
先看看一個普通的爬?。?/strong>
我們引入了模塊urllib.request,然后創(chuàng)建了一個函數(shù)downloadImage()用于下載文件,創(chuàng)建了一個函數(shù)main()用于對下載函數(shù)進(jìn)行遍歷20次。
耗時4秒多。
下面看看使用多線程的:
程序的前部分大同小異,后面我們創(chuàng)建了一個threads列表,,然后遍歷10次,創(chuàng)建一個新的線程對象,將其添加到threads列表中,然后啟動該線程。
最后,我們通過遍歷我們的threads列表來調(diào)用我們的線程,然后調(diào)用join()方法在每個線程上,這確保我們在下載完文件之前,不會執(zhí)行剩下的代碼。
運行代碼,可以發(fā)現(xiàn)程序幾乎同時啟動了10個下載任務(wù),然后在圖片下載完成后,再打印出來。
耗時0.1秒,效率提高很多。
但是需要注意的是,在網(wǎng)絡(luò)中進(jìn)行文件IO,還需要考慮網(wǎng)絡(luò)狀況和自身機(jī)器的影響,不同的網(wǎng)絡(luò)狀況下,完成的效率也不一樣。
并發(fā)數(shù)字運算
I/O密集型的任務(wù)適合于多線程,而CPU密集型的任務(wù)則適合用多進(jìn)程。
在下面的例子里,我們將找出100萬個20000到100000000之間隨機(jī)數(shù)的質(zhì)數(shù)。
順序運算:
耗時18秒。
多進(jìn)程運算:
耗時11秒。
我們分別按順序循環(huán)100萬遍和使用多進(jìn)程的進(jìn)程池循環(huán)100萬次,多進(jìn)程模式下速度提升了近7秒。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請查看下面相關(guān)鏈接
相關(guān)文章
Python源碼學(xué)習(xí)之PyType_Type和PyBaseObject_Type詳解
今天給大家?guī)淼氖顷P(guān)于Python源碼的相關(guān)知識學(xué)習(xí),文章圍繞著PyType_Type和PyBaseObject_Type展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06Python 解決相對路徑問題:"No such file or directory"
這篇文章主要介紹了Python 解決相對路徑問題:"No such file or directory"具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-06-06Python基礎(chǔ)數(shù)據(jù)類型tuple元組的概念與用法
元組(tuple)是 Python 中另一個重要的序列結(jié)構(gòu),和列表類似,元組也是由一系列按特定順序排序的元素組成,這篇文章主要給大家介紹了關(guān)于Python基礎(chǔ)數(shù)據(jù)類型tuple元組的概念與使用方法,需要的朋友可以參考下2021-07-07Python使用pyinstaller打包spec文件的方法詳解
PyInstaller是一個用于將Python腳本打包成獨立的可執(zhí)行文件的工具,使用PyInstaller您可以將Python應(yīng)用程序轉(zhuǎn)換為可執(zhí)行文件,而無需用戶安裝Python解釋器或任何額外的庫,這篇文章主要給大家介紹了關(guān)于Python使用pyinstaller打包spec文件的相關(guān)資料,需要的朋友可以參考下2024-08-08