十年網(wǎng)站開(kāi)發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專業(yè)推廣+無(wú)憂售后,網(wǎng)站問(wèn)題一站解決
基于官方文檔:
創(chuàng)新互聯(lián)公司一直在為企業(yè)提供服務(wù),多年的磨煉,使我們?cè)趧?chuàng)意設(shè)計(jì),全網(wǎng)營(yíng)銷推廣到技術(shù)研發(fā)擁有了開(kāi)發(fā)經(jīng)驗(yàn)。我們擅長(zhǎng)傾聽(tīng)企業(yè)需求,挖掘用戶對(duì)產(chǎn)品需求服務(wù)價(jià)值,為企業(yè)制作有用的創(chuàng)意設(shè)計(jì)體驗(yàn)。核心團(tuán)隊(duì)擁有超過(guò)十多年以上行業(yè)經(jīng)驗(yàn),涵蓋創(chuàng)意,策化,開(kāi)發(fā)等專業(yè)領(lǐng)域,公司涉及領(lǐng)域有基礎(chǔ)互聯(lián)網(wǎng)服務(wù)成都移動(dòng)機(jī)房、app軟件定制開(kāi)發(fā)、手機(jī)移動(dòng)建站、網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)絡(luò)整合營(yíng)銷。
日樂(lè)購(gòu),剛才看到的一個(gè)博客,寫(xiě)的都不太對(duì),還是基于官方的比較穩(wěn)妥
我就是喜歡抄官方的,哈哈
通常我們使用Process實(shí)例化一個(gè)進(jìn)程,并調(diào)用 他的 start() 方法啟動(dòng)它。
這種方法和 Thread 是一樣的。
上圖中,我寫(xiě)了 p.join() 所以主進(jìn)程是 等待 子進(jìn)程執(zhí)行完后,才執(zhí)行 print("運(yùn)行結(jié)束")
否則就是反過(guò)來(lái)了(這個(gè)不一定,看你的語(yǔ)句了,順序其實(shí)是隨機(jī)的)例如:
主進(jìn)加個(gè) sleep
所以不加join() ,其實(shí)子進(jìn)程和主進(jìn)程是各干各的,誰(shuí)也不等誰(shuí)。都執(zhí)行完后,文件運(yùn)行就結(jié)束了
上面我們用了 os.getpid() 和 os.getppid() 獲取 當(dāng)前進(jìn)程,和父進(jìn)程的id
下面就講一下,這兩個(gè)函數(shù)的用法:
os.getpid()
返回當(dāng)前進(jìn)程的id
os.getppid()
返回父進(jìn)程的id。 父進(jìn)程退出后,unix 返回初始化進(jìn)程(1)中的一個(gè)
windows返回相同的id (可能被其他進(jìn)程使用了)
這也就解釋了,為啥我上面 的程序運(yùn)行多次, 第一次打印的parentid 都是 14212 了。
而子進(jìn)程的父級(jí) process id 是調(diào)用他的那個(gè)進(jìn)程的 id : 1940
視頻筆記:
多進(jìn)程:使用大致方法:
參考: 進(jìn)程通信(pipe和queue)
pool.map (函數(shù)可以有return 也可以共享內(nèi)存或queue) 結(jié)果直接是個(gè)列表
poll.apply_async() (同map,只不過(guò)是一個(gè)進(jìn)程,返回結(jié)果用 xx.get() 獲得)
報(bào)錯(cuò):
參考 :
把 pool = Pool() 放到 if name == " main ": 下面初始化搞定。
結(jié)果:
這個(gè)肯定有解釋的
測(cè)試多進(jìn)程計(jì)算效果:
進(jìn)程池運(yùn)行:
結(jié)果:
普通計(jì)算:
我們同樣傳入 1 2 10 三個(gè)參數(shù)測(cè)試:
其實(shí)對(duì)比下來(lái)開(kāi)始快了一半的;
我們把循環(huán)里的數(shù)字去掉一個(gè) 0;
單進(jìn)程:
多進(jìn)程:
兩次測(cè)試 單進(jìn)程/進(jìn)程池 分別為 0.669 和 0.772 幾乎成正比的。
問(wèn)題 二:
視圖:
post 視圖里面
Music 類:
直接報(bào)錯(cuò):
寫(xiě)在 類里面也 在函數(shù)里用 self.pool 調(diào)用也不行,也是相同的錯(cuò)誤。
最后 把 pool = Pool 直接寫(xiě)在 search 函數(shù)里面,奇跡出現(xiàn)了:
前臺(tái)也能顯示搜索的音樂(lè)結(jié)果了
總結(jié)一點(diǎn),進(jìn)程這個(gè)東西,最好 寫(xiě)在 直接運(yùn)行的函數(shù)里面,而不是 一個(gè)函數(shù)跳來(lái)跳去。因?yàn)樽詈罂赡?是在子進(jìn)程的子進(jìn)程運(yùn)行的,這是不許的,會(huì)報(bào)錯(cuò)。
還有一點(diǎn),多進(jìn)程運(yùn)行的函數(shù)對(duì)象,不能是 lambda 函數(shù)。也許lambda 虛擬,在內(nèi)存??
使用 pool.map 子進(jìn)程 函數(shù)報(bào)錯(cuò),導(dǎo)致整個(gè) pool 掛了:
參考:
主要你要,對(duì)函數(shù)內(nèi)部捕獲錯(cuò)誤,而不能讓異常拋出就可以了。
關(guān)于map 傳多個(gè)函數(shù)參數(shù)
我一開(kāi)始,就是正常思維,多個(gè)參數(shù),搞個(gè)元祖,讓參數(shù)一一對(duì)應(yīng)不就行了:
報(bào)錯(cuò):
參考:
普通的 process 當(dāng)讓可以穿多個(gè)參數(shù),map 卻不知道咋傳的。
apply_async 和map 一樣,不知道咋傳的。
最簡(jiǎn)單的方法:
使用 starmap 而不是 map
結(jié)果:
子進(jìn)程結(jié)束
1.8399453163146973
成功拿到結(jié)果了
關(guān)于map 和 starmap 不同的地方看源碼:
關(guān)于apply_async() ,我沒(méi)找到多參數(shù)的方法,大不了用 一個(gè)迭代的 starmap 實(shí)現(xiàn)。哈哈
關(guān)于 上面源碼里面有 itertools.starmap
itertools 用法參考:
有個(gè)問(wèn)題,多進(jìn)程最好不要使用全部的 cpu , 因?yàn)檫@樣可能影響其他任務(wù),所以 在進(jìn)程池 添加 process 參數(shù) 指定,cpu 個(gè)數(shù):
上面就是預(yù)留了 一個(gè)cpu 干其他事的
后面直接使用 Queue 遇到這個(gè)問(wèn)題:
解決:
Manager().Queue() 代替 Queue()
因?yàn)?queue.get() 是堵塞型的,所以可以提前判斷是不是 空的,以免堵塞進(jìn)程。比如下面這樣:
使用 queue.empty() 空為True
如果想了解進(jìn)程 可以先看一下這一篇 python中的進(jìn)程-理論部分
python中的多線程無(wú)法利用多核優(yōu)勢(shì),如果想要充分地使用多核CPU的資源(os.cpu_count()查看),在python中大部分情況需要使用多進(jìn)程。Python提供了multiprocessing。
multiprocessing模塊用來(lái)開(kāi)啟子進(jìn)程,并在子進(jìn)程中執(zhí)行我們定制的任務(wù)(比如函數(shù)),該模塊與多線程模塊threading的編程接口類似。
multiprocessing模塊的功能眾多:支持子進(jìn)程、通信和共享數(shù)據(jù)、執(zhí)行不同形式的同步,提供了Process、Queue、Pipe、Lock等組件。
需要再次強(qiáng)調(diào)的一點(diǎn)是:與線程不同,進(jìn)程沒(méi)有任何共享狀態(tài),進(jìn)程修改的數(shù)據(jù),改動(dòng)僅限于該進(jìn)程內(nèi)。
創(chuàng)建進(jìn)程的類 :
參數(shù)介紹:
group參數(shù)未使用,值始終為None
target表示調(diào)用對(duì)象,即子進(jìn)程要執(zhí)行的任務(wù)
args表示調(diào)用對(duì)象的位置參數(shù)元組,args=(1,2,'tiga',)
kwargs表示調(diào)用對(duì)象的字典,kwargs={'name':'tiga','age':18}
name為子進(jìn)程的名稱
方法介紹:
p.start():?jiǎn)?dòng)進(jìn)程,并調(diào)用該子進(jìn)程中的p.run()
p.run():進(jìn)程啟動(dòng)時(shí)運(yùn)行的方法,正是它去調(diào)用target指定的函數(shù),我們自定義類的類中一定要實(shí)現(xiàn)該方法
p.terminate():強(qiáng)制終止進(jìn)程p,不會(huì)進(jìn)行任何清理操作,如果p創(chuàng)建了子進(jìn)程,該子進(jìn)程就成了僵尸進(jìn)程,使用該方法需要特別小心這種情況。如果p還保存了一個(gè)鎖那么也將不會(huì)被釋放,進(jìn)而導(dǎo)致死鎖
p.is_alive():如果p仍然運(yùn)行,返回True
p.join([timeout]):主線程等待p終止(強(qiáng)調(diào):是主線程處于等的狀態(tài),而p是處于運(yùn)行的狀態(tài))。timeout是可選的超時(shí)時(shí)間,需要強(qiáng)調(diào)的是,p.join只能join住start開(kāi)啟的進(jìn)程,而不能join住run開(kāi)啟的進(jìn)程
屬性介紹:
注意:在windows中Process()必須放到# if __name__ == '__main__':下
創(chuàng)建并開(kāi)啟子進(jìn)程的兩種方式
方法一:
方法二:
有了join,程序不就是串行了嗎???
terminate與is_alive
name與pid
使用Python中的線程模塊,能夠同時(shí)運(yùn)行程序的不同部分,并簡(jiǎn)化設(shè)計(jì)。如果你已經(jīng)入門Python,并且想用線程來(lái)提升程序運(yùn)行速度的話,希望這篇教程會(huì)對(duì)你有所幫助。
線程與進(jìn)程
什么是進(jìn)程
進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位 進(jìn)程是具有一定獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。每個(gè)進(jìn)程都有自己的獨(dú)立內(nèi)存空間,不同進(jìn)程通過(guò)進(jìn)程間通信來(lái)通信。由于進(jìn)程比較重量,占據(jù)獨(dú)立的內(nèi)存,所以上下文進(jìn)程間的切換開(kāi)銷(棧、寄存器、虛擬內(nèi)存、文件句柄等)比較大,但相對(duì)比較穩(wěn)定安全。
什么是線程
CPU調(diào)度和分派的基本單位 線程是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分派的基本單位,它是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位.線程自己基本上不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源(如程序計(jì)數(shù)器,一組寄存器和棧),但是它可與同屬一個(gè)進(jìn)程的其他的線程共享進(jìn)程所擁有的全部資源。線程間通信主要通過(guò)共享內(nèi)存,上下文切換很快,資源開(kāi)銷較少,但相比進(jìn)程不夠穩(wěn)定容易丟失數(shù)據(jù)。
進(jìn)程與線程的關(guān)系圖
線程與進(jìn)程的區(qū)別:
進(jìn)程
現(xiàn)實(shí)生活中,有很多的場(chǎng)景中的事情是同時(shí)進(jìn)行的,比如開(kāi)車的時(shí)候 手和腳共同來(lái)駕駛 汽車 ,比如唱歌跳舞也是同時(shí)進(jìn)行的,再比如邊吃飯邊打電話;試想如果我們吃飯的時(shí)候有一個(gè)領(lǐng)導(dǎo)來(lái)電,我們肯定是立刻就接聽(tīng)了。但是如果你吃完飯?jiān)俳勇?tīng)或者回電話,很可能會(huì)被開(kāi)除。
注意:
多任務(wù)的概念
什么叫 多任務(wù) 呢?簡(jiǎn)單地說(shuō),就是操作系統(tǒng)可以同時(shí)運(yùn)行多個(gè)任務(wù)。打個(gè)比方,你一邊在用瀏覽器上網(wǎng),一邊在聽(tīng)MP3,一邊在用Word趕作業(yè),這就是多任務(wù),至少同時(shí)有3個(gè)任務(wù)正在運(yùn)行。還有很多任務(wù)悄悄地在后臺(tái)同時(shí)運(yùn)行著,只是桌面上沒(méi)有顯示而已。
現(xiàn)在,多核CPU已經(jīng)非常普及了,但是,即使過(guò)去的單核CPU,也可以執(zhí)行多任務(wù)。由于CPU執(zhí)行代碼都是順序執(zhí)行的,那么,單核CPU是怎么執(zhí)行多任務(wù)的呢?
答案就是操作系統(tǒng)輪流讓各個(gè)任務(wù)交替執(zhí)行,任務(wù)1執(zhí)行0.01秒,切換到任務(wù)2,任務(wù)2執(zhí)行0.01秒,再切換到任務(wù)3,執(zhí)行0.01秒,這樣反復(fù)執(zhí)行下去。表面上看,每個(gè)任務(wù)都是交替執(zhí)行的,但是,由于CPU的執(zhí)行速度實(shí)在是太快了,我們感覺(jué)就像所有任務(wù)都在同時(shí)執(zhí)行一樣。
真正的并行執(zhí)行多任務(wù)只能在多核CPU上實(shí)現(xiàn),但是,由于任務(wù)數(shù)量遠(yuǎn)遠(yuǎn)多于CPU的核心數(shù)量,所以,操作系統(tǒng)也會(huì)自動(dòng)把很多任務(wù)輪流調(diào)度到每個(gè)核心上執(zhí)行。 其實(shí)就是CPU執(zhí)行速度太快啦!以至于我們感受不到在輪流調(diào)度。
并行與并發(fā)
并行(Parallelism)
并行:指兩個(gè)或兩個(gè)以上事件(或線程)在同一時(shí)刻發(fā)生,是真正意義上的不同事件或線程在同一時(shí)刻,在不同CPU資源呢上(多核),同時(shí)執(zhí)行。
特點(diǎn)
并發(fā)(Concurrency)
指一個(gè)物理CPU(也可以多個(gè)物理CPU) 在若干道程序(或線程)之間多路復(fù)用,并發(fā)性是對(duì)有限物理資源強(qiáng)制行使多用戶共享以提高效率。
特點(diǎn)
multiprocess.Process模塊
process模塊是一個(gè)創(chuàng)建進(jìn)程的模塊,借助這個(gè)模塊,就可以完成進(jìn)程的創(chuàng)建。
語(yǔ)法:Process([group [, target [, name [, args [, kwargs]]]]])
由該類實(shí)例化得到的對(duì)象,表示一個(gè)子進(jìn)程中的任務(wù)(尚未啟動(dòng))。
注意:1. 必須使用關(guān)鍵字方式來(lái)指定參數(shù);2. args指定的為傳給target函數(shù)的位置參數(shù),是一個(gè)元祖形式,必須有逗號(hào)。
參數(shù)介紹:
group:參數(shù)未使用,默認(rèn)值為None。
target:表示調(diào)用對(duì)象,即子進(jìn)程要執(zhí)行的任務(wù)。
args:表示調(diào)用的位置參數(shù)元祖。
kwargs:表示調(diào)用對(duì)象的字典。如kwargs = {'name':Jack, 'age':18}。
name:子進(jìn)程名稱。
代碼:
除了上面這些開(kāi)啟進(jìn)程的方法之外,還有一種以繼承Process的方式開(kāi)啟進(jìn)程的方式:
通過(guò)上面的研究,我們千方百計(jì)實(shí)現(xiàn)了程序的異步,讓多個(gè)任務(wù)可以同時(shí)在幾個(gè)進(jìn)程中并發(fā)處理,他們之間的運(yùn)行沒(méi)有順序,一旦開(kāi)啟也不受我們控制。盡管并發(fā)編程讓我們能更加充分的利用IO資源,但是也給我們帶來(lái)了新的問(wèn)題。
當(dāng)多個(gè)進(jìn)程使用同一份數(shù)據(jù)資源的時(shí)候,就會(huì)引發(fā)數(shù)據(jù)安全或順序混亂問(wèn)題,我們可以考慮加鎖,我們以模擬搶票為例,來(lái)看看數(shù)據(jù)安全的重要性。
加鎖可以保證多個(gè)進(jìn)程修改同一塊數(shù)據(jù)時(shí),同一時(shí)間只能有一個(gè)任務(wù)可以進(jìn)行修改,即串行的修改。加鎖犧牲了速度,但是卻保證了數(shù)據(jù)的安全。
因此我們最好找尋一種解決方案能夠兼顧:1、效率高(多個(gè)進(jìn)程共享一塊內(nèi)存的數(shù)據(jù))2、幫我們處理好鎖問(wèn)題。
mutiprocessing模塊為我們提供的基于消息的IPC通信機(jī)制:隊(duì)列和管道。隊(duì)列和管道都是將數(shù)據(jù)存放于內(nèi)存中 隊(duì)列又是基于(管道+鎖)實(shí)現(xiàn)的,可以讓我們從復(fù)雜的鎖問(wèn)題中解脫出來(lái), 我們應(yīng)該盡量避免使用共享數(shù)據(jù),盡可能使用消息傳遞和隊(duì)列,避免處理復(fù)雜的同步和鎖問(wèn)題,而且在進(jìn)程數(shù)目增多時(shí),往往可以獲得更好的可獲展性( 后續(xù)擴(kuò)展該內(nèi)容 )。
線程
Python的threading模塊
Python 供了幾個(gè)用于多線程編程的模塊,包括 thread, threading 和 Queue 等。thread 和 threading 模塊允許程序員創(chuàng)建和管理線程。thread 模塊 供了基本的線程和鎖的支持,而 threading 供了更高級(jí)別,功能更強(qiáng)的線程管理的功能。Queue 模塊允許用戶創(chuàng)建一個(gè)可以用于多個(gè)線程之間 共享數(shù)據(jù)的隊(duì)列數(shù)據(jù)結(jié)構(gòu)。
python創(chuàng)建和執(zhí)行線程
創(chuàng)建線程代碼
1. 創(chuàng)建方法一:
2. 創(chuàng)建方法二:
進(jìn)程和線程都是實(shí)現(xiàn)多任務(wù)的一種方式,例如:在同一臺(tái)計(jì)算機(jī)上能同時(shí)運(yùn)行多個(gè)QQ(進(jìn)程),一個(gè)QQ可以打開(kāi)多個(gè)聊天窗口(線程)。資源共享:進(jìn)程不能共享資源,而線程共享所在進(jìn)程的地址空間和其他資源,同時(shí),線程有自己的棧和棧指針。所以在一個(gè)進(jìn)程內(nèi)的所有線程共享全局變量,但多線程對(duì)全局變量的更改會(huì)導(dǎo)致變量值得混亂。
代碼演示:
得到的結(jié)果是:
首先需要明確的一點(diǎn)是GIL并不是Python的特性,它是在實(shí)現(xiàn)Python解析器(CPython)時(shí)所引入的一個(gè)概念。就好比C++是一套語(yǔ)言(語(yǔ)法)標(biāo)準(zhǔn),但是可以用不同的編譯器來(lái)編譯成可執(zhí)行代碼。同樣一段代碼可以通過(guò)CPython,PyPy,Psyco等不同的Python執(zhí)行環(huán)境來(lái)執(zhí)行(其中的JPython就沒(méi)有GIL)。
那么CPython實(shí)現(xiàn)中的GIL又是什么呢?GIL全稱Global Interpreter Lock為了避免誤導(dǎo),我們還是來(lái)看一下官方給出的解釋:
主要意思為:
因此,解釋器實(shí)際上被一個(gè)全局解釋器鎖保護(hù)著,它確保任何時(shí)候都只有一個(gè)Python線程執(zhí)行。在多線程環(huán)境中,Python 虛擬機(jī)按以下方式執(zhí)行:
由于GIL的存在,Python的多線程不能稱之為嚴(yán)格的多線程。因?yàn)? 多線程下每個(gè)線程在執(zhí)行的過(guò)程中都需要先獲取GIL,保證同一時(shí)刻只有一個(gè)線程在運(yùn)行。
由于GIL的存在,即使是多線程,事實(shí)上同一時(shí)刻只能保證一個(gè)線程在運(yùn)行, 既然這樣多線程的運(yùn)行效率不就和單線程一樣了嗎,那為什么還要使用多線程呢?
由于以前的電腦基本都是單核CPU,多線程和單線程幾乎看不出差別,可是由于計(jì)算機(jī)的迅速發(fā)展,現(xiàn)在的電腦幾乎都是多核CPU了,最少也是兩個(gè)核心數(shù)的,這時(shí)差別就出來(lái)了:通過(guò)之前的案例我們已經(jīng)知道,即使在多核CPU中,多線程同一時(shí)刻也只有一個(gè)線程在運(yùn)行,這樣不僅不能利用多核CPU的優(yōu)勢(shì),反而由于每個(gè)線程在多個(gè)CPU上是交替執(zhí)行的,導(dǎo)致在不同CPU上切換時(shí)造成資源的浪費(fèi),反而會(huì)更慢。即原因是一個(gè)進(jìn)程只存在一把gil鎖,當(dāng)在執(zhí)行多個(gè)線程時(shí),內(nèi)部會(huì)爭(zhēng)搶gil鎖,這會(huì)造成當(dāng)某一個(gè)線程沒(méi)有搶到鎖的時(shí)候會(huì)讓cpu等待,進(jìn)而不能合理利用多核cpu資源。
但是在使用多線程抓取網(wǎng)頁(yè)內(nèi)容時(shí),遇到IO阻塞時(shí),正在執(zhí)行的線程會(huì)暫時(shí)釋放GIL鎖,這時(shí)其它線程會(huì)利用這個(gè)空隙時(shí)間,執(zhí)行自己的代碼,因此多線程抓取比單線程抓取性能要好,所以我們還是要使用多線程的。
GIL對(duì)多線程Python程序的影響
程序的性能受到計(jì)算密集型(CPU)的程序限制和I/O密集型的程序限制影響,那什么是計(jì)算密集型和I/O密集型程序呢?
計(jì)算密集型:要進(jìn)行大量的數(shù)值計(jì)算,例如進(jìn)行上億的數(shù)字計(jì)算、計(jì)算圓周率、對(duì)視頻進(jìn)行高清解碼等等。這種計(jì)算密集型任務(wù)雖然也可以用多任務(wù)完成,但是花費(fèi)的主要時(shí)間在任務(wù)切換的時(shí)間,此時(shí)CPU執(zhí)行任務(wù)的效率比較低。
IO密集型:涉及到網(wǎng)絡(luò)請(qǐng)求(time.sleep())、磁盤IO的任務(wù)都是IO密集型任務(wù),這類任務(wù)的特點(diǎn)是CPU消耗很少,任務(wù)的大部分時(shí)間都在等待IO操作完成(因?yàn)镮O的速度遠(yuǎn)遠(yuǎn)低于CPU和內(nèi)存的速度)。對(duì)于IO密集型任務(wù),任務(wù)越多,CPU效率越高,但也有一個(gè)限度。
當(dāng)然為了避免GIL對(duì)我們程序產(chǎn)生影響,我們也可以使用,線程鎖。
LockRLock
常用的資源共享鎖機(jī)制:有Lock、RLock、Semphore、Condition等,簡(jiǎn)單給大家分享下Lock和RLock。
Lock
特點(diǎn)就是執(zhí)行速度慢,但是保證了數(shù)據(jù)的安全性
RLock
使用鎖代碼操作不當(dāng)就會(huì)產(chǎn)生死鎖的情況。
什么是死鎖
死鎖:當(dāng)線程A持有獨(dú)占鎖a,并嘗試去獲取獨(dú)占鎖b的同時(shí),線程B持有獨(dú)占鎖b,并嘗試獲取獨(dú)占鎖a的情況下,就會(huì)發(fā)生AB兩個(gè)線程由于互相持有對(duì)方需要的鎖,而發(fā)生的阻塞現(xiàn)象,我們稱為死鎖。即死鎖是指多個(gè)進(jìn)程因競(jìng)爭(zhēng)資源而造成的一種僵局,若無(wú)外力作用,這些進(jìn)程都將無(wú)法向前推進(jìn)。
所以,在系統(tǒng)設(shè)計(jì)、進(jìn)程調(diào)度等方面注意如何不讓這四個(gè)必要條件成立,如何確定資源的合理分配算法,避免進(jìn)程永久占據(jù)系統(tǒng)資源。
死鎖代碼
python線程間通信
如果各個(gè)線程之間各干各的,確實(shí)不需要通信,這樣的代碼也十分的簡(jiǎn)單。但這一般是不可能的,至少線程要和主線程進(jìn)行通信,不然計(jì)算結(jié)果等內(nèi)容無(wú)法取回。而實(shí)際情況中要復(fù)雜的多,多個(gè)線程間需要交換數(shù)據(jù),才能得到正確的執(zhí)行結(jié)果。
python中Queue是消息隊(duì)列,提供線程間通信機(jī)制,python3中重名為為queue,queue模塊塊下提供了幾個(gè)阻塞隊(duì)列,這些隊(duì)列主要用于實(shí)現(xiàn)線程通信。
在 queue 模塊下主要提供了三個(gè)類,分別代表三種隊(duì)列,它們的主要區(qū)別就在于進(jìn)隊(duì)列、出隊(duì)列的不同。
簡(jiǎn)單代碼演示
此時(shí)代碼會(huì)阻塞,因?yàn)閝ueue中內(nèi)容已滿,此時(shí)可以在第四個(gè)queue.put('蘋果')后面添加timeout,則成為 queue.put('蘋果',timeout=1)如果等待1秒鐘仍然是滿的就會(huì)拋出異常,可以捕獲異常。
同理如果隊(duì)列是空的,無(wú)法獲取到內(nèi)容默認(rèn)也會(huì)阻塞,如果不阻塞可以使用queue.get_nowait()。
在掌握了 Queue 阻塞隊(duì)列的特性之后,在下面程序中就可以利用 Queue 來(lái)實(shí)現(xiàn)線程通信了。
下面演示一個(gè)生產(chǎn)者和一個(gè)消費(fèi)者,當(dāng)然都可以多個(gè)
使用queue模塊,可在線程間進(jìn)行通信,并保證了線程安全。
協(xié)程
協(xié)程,又稱微線程,纖程。英文名Coroutine。
協(xié)程是python個(gè)中另外一種實(shí)現(xiàn)多任務(wù)的方式,只不過(guò)比線程更小占用更小執(zhí)行單元(理解為需要的資源)。為啥說(shuō)它是一個(gè)執(zhí)行單元,因?yàn)樗詭PU上下文。這樣只要在合適的時(shí)機(jī), 我們可以把一個(gè)協(xié)程 切換到另一個(gè)協(xié)程。只要這個(gè)過(guò)程中保存或恢復(fù) CPU上下文那么程序還是可以運(yùn)行的。
通俗的理解:在一個(gè)線程中的某個(gè)函數(shù),可以在任何地方保存當(dāng)前函數(shù)的一些臨時(shí)變量等信息,然后切換到另外一個(gè)函數(shù)中執(zhí)行,注意不是通過(guò)調(diào)用函數(shù)的方式做到的,并且切換的次數(shù)以及什么時(shí)候再切換到原來(lái)的函數(shù)都由開(kāi)發(fā)者自己確定。
在實(shí)現(xiàn)多任務(wù)時(shí),線程切換從系統(tǒng)層面遠(yuǎn)不止保存和恢復(fù) CPU上下文這么簡(jiǎn)單。操作系統(tǒng)為了程序運(yùn)行的高效性每個(gè)線程都有自己緩存Cache等等數(shù)據(jù),操作系統(tǒng)還會(huì)幫你做這些數(shù)據(jù)的恢復(fù)操作。所以線程的切換非常耗性能。但是協(xié)程的切換只是單純的操作CPU的上下文,所以一秒鐘切換個(gè)上百萬(wàn)次系統(tǒng)都抗的住。
greenlet與gevent
為了更好使用協(xié)程來(lái)完成多任務(wù),除了使用原生的yield完成模擬協(xié)程的工作,其實(shí)python還有的greenlet模塊和gevent模塊,使實(shí)現(xiàn)協(xié)程變的更加簡(jiǎn)單高效。
greenlet雖說(shuō)實(shí)現(xiàn)了協(xié)程,但需要我們手工切換,太麻煩了,gevent是比greenlet更強(qiáng)大的并且能夠自動(dòng)切換任務(wù)的模塊。
其原理是當(dāng)一個(gè)greenlet遇到IO(指的是input output 輸入輸出,比如網(wǎng)絡(luò)、文件操作等)操作時(shí),比如訪問(wèn)網(wǎng)絡(luò),就自動(dòng)切換到其他的greenlet,等到IO操作完成,再在適當(dāng)?shù)臅r(shí)候切換回來(lái)繼續(xù)執(zhí)行。
模擬耗時(shí)操作:
如果有耗時(shí)操作也可以換成,gevent中自己實(shí)現(xiàn)的模塊,這時(shí)候就需要打補(bǔ)丁了。
使用協(xié)程完成一個(gè)簡(jiǎn)單的二手房信息的爬蟲(chóng)代碼吧!
以下文章來(lái)源于Python專欄 ,作者宋宋
文章鏈接: