multiprocessing 是一個(gè)支持使用與 threading 模塊類似的 API 來產(chǎn)生進(jìn)程的包。 multiprocessing 包同時(shí)提供了本地和遠(yuǎn)程并發(fā)操作,通過使用子進(jìn)程而非線程有效地繞過了 全局解釋器鎖。 因此,multiprocessing 模塊允許程序員充分利用給定機(jī)器上的多個(gè)處理器。 它在 Unix 和 Windows 上均可運(yùn)行。
創(chuàng)新互聯(lián)公司專注于昂仁網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供昂仁營(yíng)銷型網(wǎng)站建設(shè),昂仁網(wǎng)站制作、昂仁網(wǎng)頁設(shè)計(jì)、昂仁網(wǎng)站官網(wǎng)定制、微信小程序服務(wù),打造昂仁網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供昂仁網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。
1、multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
2、相關(guān)方法
輸出結(jié)果如下:
Pool提供了一種快捷的方法,賦予函數(shù)并行化處理一系列輸入值的能力,可以將輸入數(shù)據(jù)分配給不同進(jìn)程處理(數(shù)據(jù)并行)。下面的例子演示了在模塊中定義此類函數(shù)的常見做法,以便子進(jìn)程可以成功導(dǎo)入該模塊。這個(gè)數(shù)據(jù)并行的基本例子使用了 Pool 。
將在標(biāo)準(zhǔn)輸出中打印
其中:
(1)p.apply(func [, args [, kwargs]]):在一個(gè)池工作進(jìn)程中執(zhí)行func( args, kwargs),然后返回結(jié)果。需要強(qiáng)調(diào)的是:此操作并不會(huì)在所有池工作進(jìn)程中并執(zhí)行func函數(shù)。如果要通過不同參數(shù)并發(fā)地執(zhí)行func函數(shù),必須從不同線程調(diào)用p.apply()函數(shù)或者使用p.apply_async()
(2)p.apply_async(func [, args [, kwargs]]):在一個(gè)池工作進(jìn)程中執(zhí)行func( args,**kwargs),然后返回結(jié)果。此方法的結(jié)果是 AsyncResult類的實(shí)例,callback是可調(diào)用對(duì)象,接收輸入?yún)?shù)。當(dāng)func的結(jié)果變?yōu)榭捎脮r(shí),將理解傳遞給callback。callback禁止執(zhí)行任何阻塞操作,否則將接收其他異步操作中的結(jié)果。多進(jìn)程并發(fā)!
(3)p.close():關(guān)閉進(jìn)程池,防止進(jìn)一步操作。如果所有操作持續(xù)掛起,它們將在工作進(jìn)程終止前完成
(4)p.jion():等待所有工作進(jìn)程退出。此方法只能在close()或teminate()之后調(diào)用
眾所周知,Python中不存在真正的多線程,Python中的多線程是一個(gè)并發(fā)過程。如果想要并行的執(zhí)行程序,充分的利用cpu資源(cpu核心),還是需要使用多進(jìn)程解決的。其中multiprocessing模塊應(yīng)該是Python中最常用的多進(jìn)程模塊了。
基本上multiprocessing這個(gè)模塊和threading這個(gè)模塊用法是相同的,也是可以通過函數(shù)和類創(chuàng)建進(jìn)程。
上述案例基本上就是筆者搬用了上篇文章多線程的案例,可見其使用的相似之處。導(dǎo)入multiprocessing后實(shí)例化Process就可以創(chuàng)建一個(gè)進(jìn)程,參數(shù)的話也是和多線程一樣,target放置進(jìn)程執(zhí)行函數(shù),args存放該函數(shù)的參數(shù)。
使用類來創(chuàng)建進(jìn)程也是需要先繼承multiprocessing.Process并且實(shí)現(xiàn)其init方法。
Pool可以提供指定數(shù)量的進(jìn)程,供用戶調(diào)用,當(dāng)有新的請(qǐng)求提交到pool中時(shí),如果池還沒有滿,那么就會(huì)創(chuàng)建一個(gè)新的進(jìn)程用來執(zhí)行該請(qǐng)求。
但如果池中的進(jìn)程數(shù)已經(jīng)達(dá)到規(guī)定最大值,那么該請(qǐng)求就會(huì)等待,直到池中有進(jìn)程結(jié)束,才會(huì)創(chuàng)建新的進(jìn)程。
需要注意的是,在調(diào)用join方法阻塞進(jìn)程前,需要先調(diào)用close方法,,否則程序會(huì)出錯(cuò)。
在上述案例中,提到了非阻塞,當(dāng)把創(chuàng)建進(jìn)程的方法換為pool.apply(func, (msg,))時(shí),就會(huì)阻塞進(jìn)程,出現(xiàn)下面的狀況。
在multiprocessing模塊中還存在Queue對(duì)象,這是一個(gè)進(jìn)程的安全隊(duì)列,近似queue.Queue。隊(duì)列一般也是需要配合多線程或者多進(jìn)程使用。
下列案例是一個(gè)使用進(jìn)程隊(duì)列實(shí)現(xiàn)的生產(chǎn)者消費(fèi)者模式。
multiprocessing支持兩種進(jìn)程間的通信,其中一種便是上述案例的隊(duì)列,另一種則稱作管道。在官方文檔的描述中,multiprocessing中的隊(duì)列是基于管道實(shí)現(xiàn)的,并且擁有更高的讀寫效率。
管道可以理解為進(jìn)程間的通道,使用Pipe([duplex])創(chuàng)建,并返回一個(gè)元組(conn1,conn2)。如果duplex被置為True(默認(rèn)值),那么該管道是雙向的,如果duplex被置為False,那么該管道是單向的,即conn1只能用于接收消息,而conn2僅能用于發(fā)送消息。
其中conn1、conn2表示管道兩端的連接對(duì)象,每個(gè)連接對(duì)象都有send()和recv()方法。send和recv方法分別是發(fā)送和接受消息的方法。例如,可以調(diào)用conn1.send發(fā)送消息,conn1.recv接收消息。如果沒有消息可接收,recv方法會(huì)一直阻塞。如果管道已經(jīng)被關(guān)閉,那么recv方法會(huì)拋出EOFError。
關(guān)于multiprocessing模塊其實(shí)還有很多實(shí)用的類和方法,由于篇幅有限(懶),筆者就先寫到這里。該模塊其實(shí)用起來很像threading模塊,像鎖對(duì)象和守護(hù)線程(進(jìn)程)等multiprocessing模塊也是有的,使用方法也近乎相同。
如果想要更加詳細(xì)的了解multiprocessing模塊,請(qǐng)參考官方文檔。
如果想了解進(jìn)程 可以先看一下這一篇 python中的進(jìn)程-理論部分
python中的多線程無法利用多核優(yōu)勢(shì),如果想要充分地使用多核CPU的資源(os.cpu_count()查看),在python中大部分情況需要使用多進(jìn)程。Python提供了multiprocessing。
multiprocessing模塊用來開啟子進(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)程沒有任何共享狀態(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開啟的進(jìn)程,而不能join住run開啟的進(jìn)程
屬性介紹:
注意:在windows中Process()必須放到# if __name__ == '__main__':下
創(chuàng)建并開啟子進(jìn)程的兩種方式
方法一:
方法二:
有了join,程序不就是串行了嗎???
terminate與is_alive
name與pid
基于官方文檔:
日樂購(gòu),剛才看到的一個(gè)博客,寫的都不太對(duì),還是基于官方的比較穩(wěn)妥
我就是喜歡抄官方的,哈哈
通常我們使用Process實(shí)例化一個(gè)進(jìn)程,并調(diào)用 他的 start() 方法啟動(dòng)它。
這種方法和 Thread 是一樣的。
上圖中,我寫了 p.join() 所以主進(jìn)程是 等待 子進(jìn)程執(zhí)行完后,才執(zhí)行 print("運(yùn)行結(jié)束")
否則就是反過來了(這個(gè)不一定,看你的語句了,順序其實(shí)是隨機(jī)的)例如:
主進(jìn)加個(gè) sleep
所以不加join() ,其實(shí)子進(jìn)程和主進(jìn)程是各干各的,誰也不等誰。都執(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,只不過是一個(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ì)比下來開始快了一半的;
我們把循環(huán)里的數(shù)字去掉一個(gè) 0;
單進(jìn)程:
多進(jìn)程:
兩次測(cè)試 單進(jìn)程/進(jìn)程池 分別為 0.669 和 0.772 幾乎成正比的。
問題 二:
視圖:
post 視圖里面
Music 類:
直接報(bào)錯(cuò):
寫在 類里面也 在函數(shù)里用 self.pool 調(diào)用也不行,也是相同的錯(cuò)誤。
最后 把 pool = Pool 直接寫在 search 函數(shù)里面,奇跡出現(xiàn)了:
前臺(tái)也能顯示搜索的音樂結(jié)果了
總結(jié)一點(diǎn),進(jìn)程這個(gè)東西,最好 寫在 直接運(yùn)行的函數(shù)里面,而不是 一個(gè)函數(shù)跳來跳去。因?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ù)
我一開始,就是正常思維,多個(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() ,我沒找到多參數(shù)的方法,大不了用 一個(gè)迭代的 starmap 實(shí)現(xiàn)。哈哈
關(guān)于 上面源碼里面有 itertools.starmap
itertools 用法參考:
有個(gè)問題,多進(jìn)程最好不要使用全部的 cpu , 因?yàn)檫@樣可能影響其他任務(wù),所以 在進(jìn)程池 添加 process 參數(shù) 指定,cpu 個(gè)數(shù):
上面就是預(yù)留了 一個(gè)cpu 干其他事的
后面直接使用 Queue 遇到這個(gè)問題:
解決:
Manager().Queue() 代替 Queue()
因?yàn)?queue.get() 是堵塞型的,所以可以提前判斷是不是 空的,以免堵塞進(jìn)程。比如下面這樣:
使用 queue.empty() 空為True
使用Python中的線程模塊,能夠同時(shí)運(yùn)行程序的不同部分,并簡(jiǎn)化設(shè)計(jì)。如果你已經(jīng)入門Python,并且想用線程來提升程序運(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)程通過進(jìn)程間通信來通信。由于進(jìn)程比較重量,占據(jù)獨(dú)立的內(nèi)存,所以上下文進(jìn)程間的切換開銷(棧、寄存器、虛擬內(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)程所擁有的全部資源。線程間通信主要通過共享內(nèi)存,上下文切換很快,資源開銷較少,但相比進(jìn)程不夠穩(wěn)定容易丟失數(shù)據(jù)。
進(jìn)程與線程的關(guān)系圖
線程與進(jìn)程的區(qū)別:
進(jìn)程
現(xiàn)實(shí)生活中,有很多的場(chǎng)景中的事情是同時(shí)進(jìn)行的,比如開車的時(shí)候 手和腳共同來駕駛 汽車 ,比如唱歌跳舞也是同時(shí)進(jìn)行的,再比如邊吃飯邊打電話;試想如果我們吃飯的時(shí)候有一個(gè)領(lǐng)導(dǎo)來電,我們肯定是立刻就接聽了。但是如果你吃完飯?jiān)俳勇牷蛘呋仉娫挘芸赡軙?huì)被開除。
注意:
多任務(wù)的概念
什么叫 多任務(wù) 呢?簡(jiǎn)單地說,就是操作系統(tǒng)可以同時(shí)運(yùn)行多個(gè)任務(wù)。打個(gè)比方,你一邊在用瀏覽器上網(wǎng),一邊在聽MP3,一邊在用Word趕作業(yè),這就是多任務(wù),至少同時(shí)有3個(gè)任務(wù)正在運(yùn)行。還有很多任務(wù)悄悄地在后臺(tái)同時(shí)運(yùn)行著,只是桌面上沒有顯示而已。
現(xiàn)在,多核CPU已經(jīng)非常普及了,但是,即使過去的單核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í)在是太快了,我們感覺就像所有任務(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)建。
語法:Process([group [, target [, name [, args [, kwargs]]]]])
由該類實(shí)例化得到的對(duì)象,表示一個(gè)子進(jìn)程中的任務(wù)(尚未啟動(dòng))。
注意:1. 必須使用關(guān)鍵字方式來指定參數(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)程名稱。
代碼:
除了上面這些開啟進(jìn)程的方法之外,還有一種以繼承Process的方式開啟進(jìn)程的方式:
通過上面的研究,我們千方百計(jì)實(shí)現(xiàn)了程序的異步,讓多個(gè)任務(wù)可以同時(shí)在幾個(gè)進(jìn)程中并發(fā)處理,他們之間的運(yùn)行沒有順序,一旦開啟也不受我們控制。盡管并發(fā)編程讓我們能更加充分的利用IO資源,但是也給我們帶來了新的問題。
當(dāng)多個(gè)進(jìn)程使用同一份數(shù)據(jù)資源的時(shí)候,就會(huì)引發(fā)數(shù)據(jù)安全或順序混亂問題,我們可以考慮加鎖,我們以模擬搶票為例,來看看數(shù)據(jù)安全的重要性。
加鎖可以保證多個(gè)進(jìn)程修改同一塊數(shù)據(jù)時(shí),同一時(shí)間只能有一個(gè)任務(wù)可以進(jìn)行修改,即串行的修改。加鎖犧牲了速度,但是卻保證了數(shù)據(jù)的安全。
因此我們最好找尋一種解決方案能夠兼顧:1、效率高(多個(gè)進(jìn)程共享一塊內(nèi)存的數(shù)據(jù))2、幫我們處理好鎖問題。
mutiprocessing模塊為我們提供的基于消息的IPC通信機(jī)制:隊(duì)列和管道。隊(duì)列和管道都是將數(shù)據(jù)存放于內(nèi)存中 隊(duì)列又是基于(管道+鎖)實(shí)現(xiàn)的,可以讓我們從復(fù)雜的鎖問題中解脫出來, 我們應(yīng)該盡量避免使用共享數(shù)據(jù),盡可能使用消息傳遞和隊(duì)列,避免處理復(fù)雜的同步和鎖問題,而且在進(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可以打開多個(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++是一套語言(語法)標(biāo)準(zhǔn),但是可以用不同的編譯器來編譯成可執(zhí)行代碼。同樣一段代碼可以通過CPython,PyPy,Psyco等不同的Python執(zhí)行環(huán)境來執(zhí)行(其中的JPython就沒有GIL)。
那么CPython實(shí)現(xiàn)中的GIL又是什么呢?GIL全稱Global Interpreter Lock為了避免誤導(dǎo),我們還是來看一下官方給出的解釋:
主要意思為:
因此,解釋器實(shí)際上被一個(gè)全局解釋器鎖保護(hù)著,它確保任何時(shí)候都只有一個(gè)Python線程執(zhí)行。在多線程環(huán)境中,Python 虛擬機(jī)按以下方式執(zhí)行:
由于GIL的存在,Python的多線程不能稱之為嚴(yán)格的多線程。因?yàn)? 多線程下每個(gè)線程在執(zhí)行的過程中都需要先獲取GIL,保證同一時(shí)刻只有一個(gè)線程在運(yùn)行。
由于GIL的存在,即使是多線程,事實(shí)上同一時(shí)刻只能保證一個(gè)線程在運(yùn)行, 既然這樣多線程的運(yùn)行效率不就和單線程一樣了嗎,那為什么還要使用多線程呢?
由于以前的電腦基本都是單核CPU,多線程和單線程幾乎看不出差別,可是由于計(jì)算機(jī)的迅速發(fā)展,現(xiàn)在的電腦幾乎都是多核CPU了,最少也是兩個(gè)核心數(shù)的,這時(shí)差別就出來了:通過之前的案例我們已經(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è)線程沒有搶到鎖的時(shí)候會(huì)讓cpu等待,進(jìn)而不能合理利用多核cpu資源。
但是在使用多線程抓取網(wǎng)頁內(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)資源而造成的一種僵局,若無外力作用,這些進(jìn)程都將無法向前推進(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)容無法取回。而實(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ì)列是空的,無法獲取到內(nèi)容默認(rèn)也會(huì)阻塞,如果不阻塞可以使用queue.get_nowait()。
在掌握了 Queue 阻塞隊(duì)列的特性之后,在下面程序中就可以利用 Queue 來實(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ù)的方式,只不過比線程更小占用更小執(zhí)行單元(理解為需要的資源)。為啥說它是一個(gè)執(zhí)行單元,因?yàn)樗詭PU上下文。這樣只要在合適的時(shí)機(jī), 我們可以把一個(gè)協(xié)程 切換到另一個(gè)協(xié)程。只要這個(gè)過程中保存或恢復(fù) CPU上下文那么程序還是可以運(yùn)行的。
通俗的理解:在一個(gè)線程中的某個(gè)函數(shù),可以在任何地方保存當(dāng)前函數(shù)的一些臨時(shí)變量等信息,然后切換到另外一個(gè)函數(shù)中執(zhí)行,注意不是通過調(diào)用函數(shù)的方式做到的,并且切換的次數(shù)以及什么時(shí)候再切換到原來的函數(shù)都由開發(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è)上百萬次系統(tǒng)都抗的住。
greenlet與gevent
為了更好使用協(xié)程來完成多任務(wù),除了使用原生的yield完成模擬協(xié)程的工作,其實(shí)python還有的greenlet模塊和gevent模塊,使實(shí)現(xiàn)協(xié)程變的更加簡(jiǎn)單高效。
greenlet雖說實(shí)現(xiàn)了協(xié)程,但需要我們手工切換,太麻煩了,gevent是比greenlet更強(qiáng)大的并且能夠自動(dòng)切換任務(wù)的模塊。
其原理是當(dāng)一個(gè)greenlet遇到IO(指的是input output 輸入輸出,比如網(wǎng)絡(luò)、文件操作等)操作時(shí),比如訪問網(wǎng)絡(luò),就自動(dòng)切換到其他的greenlet,等到IO操作完成,再在適當(dāng)?shù)臅r(shí)候切換回來繼續(xù)執(zhí)行。
模擬耗時(shí)操作:
如果有耗時(shí)操作也可以換成,gevent中自己實(shí)現(xiàn)的模塊,這時(shí)候就需要打補(bǔ)丁了。
使用協(xié)程完成一個(gè)簡(jiǎn)單的二手房信息的爬蟲代碼吧!
以下文章來源于Python專欄 ,作者宋宋
文章鏈接:
新聞標(biāo)題:python的進(jìn)程函數(shù) python的線程和進(jìn)程
網(wǎng)站地址:http://jinyejixie.com/article14/dodopge.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供ChatGPT、商城網(wǎng)站、移動(dòng)網(wǎng)站建設(shè)、企業(yè)網(wǎng)站制作、網(wǎng)站改版、定制網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)