GIL , 线程池 , 同步 , 异步 , 队列 , 事件
官方解釋: ''' In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.) '''釋義: 在CPython中,這個(gè)全局解釋器鎖,也稱為GIL,是一個(gè)互斥鎖,防止多個(gè)線程在同一時(shí)間執(zhí)行Python字節(jié)碼,這個(gè)鎖是非常重要的,
因?yàn)镃Python的內(nèi)存管理非線程安全的,很多其他的特性依賴于GIL,所以即使它影響了程序效率也無(wú)法將其直接去除 總結(jié): 在CPython中,GIL會(huì)把線程的并行變成串行,導(dǎo)致效率降低
?
?
?
二.GIL帶來(lái)的問(wèn)題
首先必須明確執(zhí)行一個(gè)py文件,分為三個(gè)步驟
從硬盤加載Python解釋器到內(nèi)存
從硬盤加載py文件到內(nèi)存
解釋器解析py文件內(nèi)容,交給CPU執(zhí)行
其次需要明確的是每當(dāng)執(zhí)行一個(gè)py文件,就會(huì)立即啟動(dòng)一個(gè)python解釋器,
當(dāng)執(zhí)行test.py時(shí)其內(nèi)存結(jié)構(gòu)如下:
GIL,叫做全局解釋器鎖,加到了解釋器上,并且是一把互斥鎖,那么這把鎖對(duì)應(yīng)用程序到底有什么影響?
這就需要知道解釋器的作用,以及解釋器與應(yīng)用程序代碼之間的關(guān)系
py文件中的內(nèi)容本質(zhì)都是字符串,只有在被解釋器解釋時(shí),才具備語(yǔ)法意義,解釋器會(huì)將py代碼翻譯為當(dāng)前系統(tǒng)支持的指令交給系統(tǒng)執(zhí)行。
當(dāng)進(jìn)程中僅存在一條線程時(shí),GIL鎖的存在沒(méi)有不會(huì)有任何影響,但是如果進(jìn)程中有多個(gè)線程時(shí),GIL鎖就開(kāi)始發(fā)揮作用了。如下圖:
開(kāi)啟子線程時(shí),給子線程指定了一個(gè)target表示該子線程要處理的任務(wù)即要執(zhí)行的代碼。代碼要執(zhí)行則必須交由解釋器,即多個(gè)線程之間就需要共享解釋器,為了避免共享帶來(lái)的數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題,于是就給解釋器加上了互斥鎖!
由于互斥鎖的特性,程序串行,保證數(shù)據(jù)安全,降低執(zhí)行效率,GIL將使得程序整體效率降低!
?
?
GIL與GC的孽緣 :
在使用Python中進(jìn)行編程時(shí),程序員無(wú)需參與內(nèi)存的管理工作,這是因?yàn)镻ython有自帶的內(nèi)存管理機(jī)制,簡(jiǎn)稱GC。那么GC與GIL有什么關(guān)聯(lián)?
要搞清楚這個(gè)問(wèn)題,需先了解GC的工作原理,Python中內(nèi)存管理使用的是引用計(jì)數(shù),每個(gè)數(shù)會(huì)被加上一個(gè)整型的計(jì)數(shù)器,表示這個(gè)數(shù)據(jù)被引用的次數(shù),當(dāng)這個(gè)整數(shù)變?yōu)?時(shí)則表示該數(shù)據(jù)已經(jīng)沒(méi)有人使用,成了垃圾數(shù)據(jù)。
當(dāng)內(nèi)存占用達(dá)到某個(gè)閾值時(shí),GC會(huì)將其他線程掛起,然后執(zhí)行垃圾清理操作,垃圾清理也是一串代碼,也就需要一條線程來(lái)執(zhí)行。
?
示例代碼:
from threading import Thread def task():a = 10print(a)# 開(kāi)啟三個(gè)子線程執(zhí)行task函數(shù) Thread(target=task).start() Thread(target=task).start() Thread(target=task).start()?
通過(guò)上圖可以看出,GC與其他線程都在競(jìng)爭(zhēng)解釋器的執(zhí)行權(quán),而CPU何時(shí)切換,以及切換到哪個(gè)線程都是無(wú)法預(yù)支的,這樣一來(lái)就造成了競(jìng)爭(zhēng)問(wèn)題 !
假設(shè)線程1正在定義變量a=10,而定義變量第一步會(huì)先到到內(nèi)存中申請(qǐng)空間把10存進(jìn)去,第二步將10的內(nèi)存地址與變量名a進(jìn)行綁定,如果在執(zhí)行完第一步后,CPU切換到了GC線程,GC線程發(fā)現(xiàn)10的地址引用計(jì)數(shù)為0則將其當(dāng)成垃圾進(jìn)行了清理,等CPU再次切換到線程1時(shí),剛剛保存的數(shù)據(jù)10已經(jīng)被清理掉了,導(dǎo)致無(wú)法正常定義變量。
當(dāng)然其他一些涉及到內(nèi)存的操作同樣可能產(chǎn)生問(wèn)題,為了避免GC與其他線程競(jìng)爭(zhēng)解釋器帶來(lái)的問(wèn)題,CPython簡(jiǎn)單粗暴的給解釋器加了互斥鎖
?
如下圖所示:
有了GIL后,多個(gè)線程將不可能在同一時(shí)間使用解釋器,從而保證了解釋器的數(shù)據(jù)安全。
?
?
GIL的加鎖與解鎖時(shí)機(jī)
加鎖的時(shí)機(jī):
在調(diào)用解釋器時(shí)立即加鎖
解鎖時(shí)機(jī):
-
當(dāng)前線程遇到了IO時(shí)釋放
-
當(dāng)前線程執(zhí)行時(shí)間超過(guò)設(shè)定值時(shí)釋放
?
?
但我們并不能因此就否認(rèn)Python這門語(yǔ)言,其原因如下:
GIL僅僅在CPython解釋器中存在,在其他的解釋器中沒(méi)有,并不是Python這門語(yǔ)言的缺點(diǎn)
在單核處理器下,多線程之間本來(lái)就無(wú)法真正的并行執(zhí)行
在多核處理下,運(yùn)算效率的確是比單核處理器高,但是要知道現(xiàn)代應(yīng)用程序多數(shù)都是基于網(wǎng)絡(luò)的(qq,微信,爬蟲,瀏覽器等等),CPU的運(yùn)行效率是無(wú)法決定網(wǎng)絡(luò)速度的,而網(wǎng)絡(luò)的速度是遠(yuǎn)遠(yuǎn)比不上處理器的運(yùn)算速度,則意味著每次處理器在執(zhí)行運(yùn)算前都需要等待網(wǎng)絡(luò)IO,這樣一來(lái)多核優(yōu)勢(shì)也就沒(méi)有那么明顯了
舉個(gè)例子:
任務(wù)1 從網(wǎng)絡(luò)上下載一個(gè)網(wǎng)頁(yè),等待網(wǎng)絡(luò)IO的時(shí)間為1分鐘,解析網(wǎng)頁(yè)數(shù)據(jù)花費(fèi),1秒鐘
任務(wù)2 將用戶輸入數(shù)據(jù)并將其轉(zhuǎn)換為大寫,等待用戶輸入時(shí)間為1分鐘,轉(zhuǎn)換為大寫花費(fèi),1秒鐘
單核CPU下:1.開(kāi)啟第一個(gè)任務(wù)后進(jìn)入等待。2.切換到第二個(gè)任務(wù)也進(jìn)入了等待。一分鐘后解析網(wǎng)頁(yè)數(shù)據(jù)花費(fèi)1秒解析完成切換到第二個(gè)任務(wù),轉(zhuǎn)換為大寫花費(fèi)1秒,那么總耗時(shí)為:1分+1秒+1秒 = 1分鐘2秒
多核CPU下:1.CPU1處理第一個(gè)任務(wù)等待1分鐘,解析花費(fèi)1秒鐘。1.CPU2處理第二個(gè)任務(wù)等待1分鐘,轉(zhuǎn)換大寫花費(fèi)1秒鐘。由于兩個(gè)任務(wù)是并行執(zhí)行的所以總的執(zhí)行時(shí)間為1分鐘+1秒鐘 = 1分鐘1秒
可以發(fā)現(xiàn),多核CPU對(duì)于總的執(zhí)行時(shí)間提升只有1秒,但是這邊的1秒實(shí)際上是夸張了,轉(zhuǎn)換大寫操作不可能需要1秒,時(shí)間非常短!
上面的兩個(gè)任務(wù)都是需要大量IO時(shí)間的,這樣的任務(wù)稱之為IO密集型,與之對(duì)應(yīng)的是計(jì)算密集型即IO操作較少大部分都是計(jì)算任務(wù)。
對(duì)于計(jì)算密集型任務(wù),Python多線程的確比不上其他語(yǔ)言!為了解決這個(gè)弊端,Python推出了多進(jìn)程技術(shù),可以良好的利用多核處理器來(lái)完成計(jì)算密集任務(wù)。
總結(jié):
1.單核下無(wú)論是IO密集還是計(jì)算密集GIL都不會(huì)產(chǎn)生任何影響
3.Cpython中IO密集任務(wù)應(yīng)該采用多線程,計(jì)算密集型應(yīng)該采用多進(jìn)程
另外:之所以廣泛采用CPython解釋器,就是因?yàn)榇罅康膽?yīng)用程序都是IO密集型的,還有另一個(gè)很重要的原因是CPython可以無(wú)縫對(duì)接各種C語(yǔ)言實(shí)現(xiàn)的庫(kù),這對(duì)于一些數(shù)學(xué)計(jì)算相關(guān)的應(yīng)用程序而言非常的happy,直接就能使用各種現(xiàn)成的算法
?
計(jì)算密集型的效率測(cè)試:
from multiprocessing import Process from threading import Thread import timedef task():for i in range(10000000):i += 1if __name__ == '__main__':start_time = time.time()# 多進(jìn)程p1 = Process(target=task) # 2.053471565246582p2 = Process(target=task)p3 = Process(target=task)p4 = Process(target=task)# 多線程# p1 = Thread(target=task) # 3.169567823410034# p2 = Thread(target=task)# p3 = Thread(target=task)# p4 = Thread(target=task) p1.start()p2.start()p3.start()p4.start()p1.join()p2.join()p3.join()p4.join()print(time.time() - start_time)
?
IO密集型的效率測(cè)試 :
from multiprocessing import Process from threading import Thread import time def task():with open("test.txt",encoding="utf-8") as f:f.read() if __name__ == '__main__':start_time = time.time()# 多進(jìn)程# p1 = Process(target=task)# p2 = Process(target=task)# p3 = Process(target=task)# p4 = Process(target=task)# 多線程p1 = Thread(target=task)p2 = Thread(target=task)p3 = Thread(target=task)p4 = Thread(target=task)p1.start()p2.start()p3.start()p4.start()p1.join()p2.join()p3.join()p4.join()print(time.time()-start_time)?
?
五.自定義的線程鎖與GIL的區(qū)別
GIL保護(hù)的是解釋器級(jí)別的數(shù)據(jù)安全,比如對(duì)象的引用計(jì)數(shù),垃圾分代數(shù)據(jù)等等,具體參考垃圾回收機(jī)制詳解。
from threading import Thread,Lock import timea = 0 def task():global atemp = atime.sleep(0.01) a = temp + 1t1 = Thread(target=task) t2 = Thread(target=task) t1.start() t2.start()t1.join() t2.join() print(a)
?
過(guò)程分析:
1.線程1獲得CPU執(zhí)行權(quán),并獲取GIL鎖執(zhí)行代碼 ,得到a的值為0后進(jìn)入睡眠,釋放CPU并釋放GIL
2.線程2獲得CPU執(zhí)行權(quán),并獲取GIL鎖執(zhí)行代碼 ,得到a的值為0后進(jìn)入睡眠,釋放CPU并釋放GIL
3.線程1睡醒后獲得CPU執(zhí)行權(quán),并獲取GIL執(zhí)行代碼 ,將temp的值0+1后賦給a,執(zhí)行完畢釋放CPU并釋放GIL
4.線程2睡醒后獲得CPU執(zhí)行權(quán),并獲取GIL執(zhí)行代碼 ,將temp的值0+1后賦給a,執(zhí)行完畢釋放CPU并釋放GIL,最后a的值也就是1
?
from threading import Thread,Lock import timelock = Lock() a = 0 def task():global alock.acquire()temp = atime.sleep(0.01)a = temp + 1lock.release() t1 = Thread(target=task) t2 = Thread(target=task)t1.start() t2.start()t1.join() t2.join() print(a)?
過(guò)程分析:
1.線程1獲得CPU執(zhí)行權(quán),并獲取GIL鎖執(zhí)行代碼 ,得到a的值為0后進(jìn)入睡眠,釋放CPU并釋放GIL,不釋放lock
2.線程2獲得CPU執(zhí)行權(quán),并獲取GIL鎖,嘗試獲取lock失敗,無(wú)法執(zhí)行,釋放CPU并釋放GIL
3.線程1睡醒后獲得CPU執(zhí)行權(quán),并獲取GIL繼續(xù)執(zhí)行代碼 ,將temp的值0+1后賦給a,執(zhí)行完畢釋放CPU釋放GIL,釋放lock,此時(shí)a的值為1
4.線程2獲得CPU執(zhí)行權(quán),獲取GIL鎖,嘗試獲取lock成功,執(zhí)行代碼,得到a的值為1后進(jìn)入睡眠,釋放CPU并釋放GIL,不釋放lock
5.線程2睡醒后獲得CPU執(zhí)行權(quán),獲取GIL繼續(xù)執(zhí)行代碼 ,將temp的值1+1后賦給a,執(zhí)行完畢釋放CPU釋放GIL,釋放lock,此時(shí)a的值為2
?
?
?
?
六:進(jìn)程池與線程池
什么是進(jìn)程/線程池?
池表示一個(gè)容器,本質(zhì)上就是一個(gè)存儲(chǔ)進(jìn)程或線程的列表
?
池子中存儲(chǔ)線程還是進(jìn)程?
?
為什么需要進(jìn)程/線程池?
在很多情況下需要控制進(jìn)程或線程的數(shù)量在一個(gè)合理的范圍,例如TCP程序中,一個(gè)客戶端對(duì)應(yīng)一個(gè)線程,雖然線程的開(kāi)銷小,但肯定不能無(wú)限的開(kāi),否則系統(tǒng)資源遲早被耗盡,解決的辦法就是控制線程的數(shù)量。
線程/進(jìn)程池不僅幫我們控制線程/進(jìn)程的數(shù)量,還幫我們完成了線程/進(jìn)程的創(chuàng)建,銷毀,以及任務(wù)的分配
?
進(jìn)程池的使用:
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor import time,os# 創(chuàng)建進(jìn)程池,指定最大進(jìn)程數(shù)為3,此時(shí)不會(huì)創(chuàng)建進(jìn)程,不指定數(shù)量時(shí),默認(rèn)為CPU和核數(shù) pool = ProcessPoolExecutor(3)def task():time.sleep(1)print(os.getpid(),"working..")if __name__ == '__main__':for i in range(10):pool.submit(task) # 提交任務(wù)時(shí)立即創(chuàng)建進(jìn)程# 任務(wù)執(zhí)行完成后也不會(huì)立即銷毀進(jìn)程time.sleep(2)for i in range(10):pool.submit(task) #再有新任務(wù)是 直接使用之前已經(jīng)創(chuàng)建好的進(jìn)程來(lái)執(zhí)行?
線程池的使用:
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor from threading import current_thread,active_count import time,os# 創(chuàng)建進(jìn)程池,指定最大線程數(shù)為3,此時(shí)不會(huì)創(chuàng)建線程,不指定數(shù)量時(shí),默認(rèn)為CPU和核數(shù)*5 pool = ThreadPoolExecutor(3) print(active_count()) # 只有一個(gè)主線def task():time.sleep(1)print(current_thread().name,"working..")if __name__ == '__main__':for i in range(10):pool.submit(task) # 第一次提交任務(wù)時(shí)立即創(chuàng)建線程# 任務(wù)執(zhí)行完成后也不會(huì)立即銷毀time.sleep(2)for i in range(10):pool.submit(task) #再有新任務(wù)時(shí) 直接使用之前已經(jīng)創(chuàng)建好的線程來(lái)執(zhí)行?
案例:TCP中的應(yīng)用
?
?
?
七.同步異步-阻塞非阻塞
同步異步-阻塞非阻塞,經(jīng)常會(huì)被程序員提及,并且概念非常容易混淆!
?
阻塞非阻塞 ------指的是程序的運(yùn)行狀態(tài)
阻塞:當(dāng)程序執(zhí)行過(guò)程中遇到了IO操作,在執(zhí)行IO操作時(shí),程序無(wú)法繼續(xù)執(zhí)行其他代碼,稱為阻塞!
非阻塞:程序在正常運(yùn)行沒(méi)有遇到IO操作,或者通過(guò)某種方式使程序即使遇到了也不會(huì)停在原地,還可以執(zhí)行其他操作,以提高CPU的占用率
?
同步-異步-------- 指的是提交任務(wù)的方式
同步:指調(diào)用發(fā)起任務(wù)后必須在原地等待任務(wù)執(zhí)行完成,才能繼續(xù)執(zhí)行
異步:指調(diào)用發(fā)起任務(wù)后不用等待任務(wù)執(zhí)行,可以立即開(kāi)啟執(zhí)行其他操作
?
同步會(huì)有等待的效果但是這和阻塞是完全不同的,阻塞時(shí)程序會(huì)被剝奪CPU執(zhí)行權(quán),而同步調(diào)用則不會(huì)!
?
?
程序中的異步調(diào)用并獲取結(jié)果方式1:
from concurrent.futures import ThreadPoolExecutor from threading import current_thread import timepool = ThreadPoolExecutor(3) def task(i):time.sleep(0.01)print(current_thread().name,"working..")return i ** iif __name__ == '__main__':objs = []for i in range(3):res_obj = pool.submit(task,i) # 異步方式提交任務(wù)# 會(huì)返回一個(gè)對(duì)象用于表示任務(wù)結(jié)果 objs.append(res_obj)# 該函數(shù)默認(rèn)是阻塞的 會(huì)等待池子中所有任務(wù)執(zhí)行結(jié)束后執(zhí)行 pool.shutdown(wait=True)# 從結(jié)果對(duì)象中取出執(zhí)行結(jié)果 for res_obj in objs:print(res_obj.result()) print("over")?
程序中的異步調(diào)用并獲取結(jié)果方式2:
from concurrent.futures import ThreadPoolExecutor from threading import current_thread import timepool = ThreadPoolExecutor(3) def task(i):time.sleep(0.01)print(current_thread().name,"working..")return i ** iif __name__ == '__main__':objs = []for i in range(3):res_obj = pool.submit(task,i) # 會(huì)返回一個(gè)對(duì)象用于表示任務(wù)結(jié)果print(res_obj.result()) #result是同步的一旦調(diào)用就必須等待 任務(wù)執(zhí)行完成拿到結(jié)果 print("over")?
?
?
8.異步回調(diào)
什么是異步回調(diào)
異步回調(diào)指的是:在發(fā)起一個(gè)異步任務(wù)的同時(shí)指定一個(gè)函數(shù),在異步任務(wù)完成時(shí)會(huì)自動(dòng)的調(diào)用這個(gè)函數(shù)
?
為什么需要異步回調(diào)
之前在使用線程池或進(jìn)程池提交任務(wù)時(shí),如果想要處理任務(wù)的執(zhí)行結(jié)果則必須調(diào)用result函數(shù)或是shutdown函數(shù),而它們都是是阻塞的,會(huì)等到任務(wù)執(zhí)行完畢后才能繼續(xù)執(zhí)行,這樣一來(lái)在這個(gè)等待過(guò)程中就無(wú)法執(zhí)行其他任務(wù),降低了效率,所以需要一種方案,即保證解析結(jié)果的線程不用等待,又能保證數(shù)據(jù)能夠及時(shí)被解析,該方案就是異步回調(diào)
?
異步回調(diào)的使用
先來(lái)看一個(gè)案例:
在編寫爬蟲程序時(shí),通常都是兩個(gè)步驟:
1.從服務(wù)器下載一個(gè)網(wǎng)頁(yè)文件
2.讀取并且解析文件內(nèi)容,提取有用的數(shù)據(jù)
按照以上流程可以編寫一個(gè)簡(jiǎn)單的爬蟲程序
要請(qǐng)求網(wǎng)頁(yè)數(shù)據(jù)則需要使用到第三方的請(qǐng)求庫(kù)requests可以通過(guò)pip或是pycharm來(lái)安裝,在pycharm中點(diǎn)擊settings->解釋器->點(diǎn)擊+號(hào)->搜索requests->安裝
?
import requests,re,os,random,time from concurrent.futures import ProcessPoolExecutordef get_data(url):print("%s 正在請(qǐng)求%s" % (os.getpid(),url))time.sleep(random.randint(1,2))response = requests.get(url)print(os.getpid(),"請(qǐng)求成功 數(shù)據(jù)長(zhǎng)度",len(response.content))#parser(response) # 3.直接調(diào)用解析方法 哪個(gè)進(jìn)程請(qǐng)求完成就那個(gè)進(jìn)程解析數(shù)據(jù) 強(qiáng)行使兩個(gè)操作耦合到一起了return responsedef parser(obj):data = obj.result()htm = data.content.decode("utf-8")ls = re.findall("href=.*?com",htm)print(os.getpid(),"解析成功",len(ls),"個(gè)鏈接")if __name__ == '__main__':pool = ProcessPoolExecutor(3)urls = ["https://www.baidu.com","https://www.sina.com","https://www.python.org","https://www.tmall.com","https://www.mysql.com","https://www.apple.com.cn"]# objs = []for url in urls:# res = pool.submit(get_data,url).result() # 1.同步的方式獲取結(jié)果 將導(dǎo)致所有請(qǐng)求任務(wù)不能并發(fā)# parser(res) obj = pool.submit(get_data,url) # obj.add_done_callback(parser) # 4.使用異步回調(diào),保證了數(shù)據(jù)可以被及時(shí)處理,并且請(qǐng)求和解析解開(kāi)了耦合# objs.append(obj)# pool.shutdown() # 2.等待所有任務(wù)執(zhí)行結(jié)束在統(tǒng)一的解析# for obj in objs:# res = obj.result()# parser(res)# 1.請(qǐng)求任務(wù)可以并發(fā) 但是結(jié)果不能被及時(shí)解析 必須等所有請(qǐng)求完成才能解析# 2.解析任務(wù)變成了串行?
總結(jié):異步回調(diào)使用方法就是在提交任務(wù)后得到一個(gè)Futures對(duì)象,調(diào)用對(duì)象的add_done_callback來(lái)指定一個(gè)回調(diào)函數(shù),
如果把任務(wù)比喻為燒水,沒(méi)有回調(diào)時(shí)就只能守著水壺等待水開(kāi),有了回調(diào)相當(dāng)于換了一個(gè)會(huì)響的水壺,燒水期間可用作其他的事情,等待水開(kāi)了水壺會(huì)自動(dòng)發(fā)出聲音,這時(shí)候再回來(lái)處理。水壺自動(dòng)發(fā)出聲音就是回調(diào)。
使用進(jìn)程池時(shí),回調(diào)函數(shù)都是主進(jìn)程中執(zhí)行執(zhí)行
使用線程池時(shí),回調(diào)函數(shù)的執(zhí)行線程是不確定的,哪個(gè)線程空閑就交給哪個(gè)線程
回調(diào)函數(shù)默認(rèn)接收一個(gè)參數(shù)就是這個(gè)任務(wù)對(duì)象自己,再通過(guò)對(duì)象的result函數(shù)來(lái)獲取任務(wù)的處理結(jié)果
?
?
?
1.Queue 先進(jìn)先出隊(duì)列
與多進(jìn)程中的Queue使用方式完全相同,區(qū)別僅僅是不能被多進(jìn)程共享。
q = Queue(3) q.put(1) q.put(2) q.put(3) print(q.get(timeout=1)) print(q.get(timeout=1)) print(q.get(timeout=1))?
2.LifoQueue 后進(jìn)先出隊(duì)列
該隊(duì)列可以模擬堆棧,實(shí)現(xiàn)先進(jìn)后出,后進(jìn)先出
lq = LifoQueue()lq.put(1) lq.put(2) lq.put(3)print(lq.get()) print(lq.get()) print(lq.get())?
3.PriorityQueue 優(yōu)先級(jí)隊(duì)列
該隊(duì)列可以為每個(gè)元素指定一個(gè)優(yōu)先級(jí),這個(gè)優(yōu)先級(jí)可以是數(shù)字,字符串或其他類型,但是必須是可以比較大小的類型,取出數(shù)據(jù)時(shí)會(huì)按照從小到大的順序取出
pq = PriorityQueue() # 數(shù)字優(yōu)先級(jí) pq.put((10,"a")) pq.put((11,"a")) pq.put((-11111,"a"))print(pq.get()) print(pq.get()) print(pq.get()) # 字符串優(yōu)先級(jí) pq.put(("b","a")) pq.put(("c","a")) pq.put(("a","a"))print(pq.get()) print(pq.get()) print(pq.get())?
?
10.線程事件Event
什么是事件
事件表示在某個(gè)時(shí)間發(fā)生了某個(gè)事情的通知信號(hào),用于線程間協(xié)同工作。
因?yàn)椴煌€程之間是獨(dú)立運(yùn)行的狀態(tài)不可預(yù)測(cè),所以一個(gè)線程與另一個(gè)線程間的數(shù)據(jù)是不同步的,當(dāng)一個(gè)線程需要利用另一個(gè)線程的狀態(tài)來(lái)確定自己的下一步操作時(shí),就必須保持線程間數(shù)據(jù)的同步,Event就可以實(shí)現(xiàn)線程間同步
Event介紹
?
可用方法:
event.isSet() #:返回event的狀態(tài)值; event.wait() #:將阻塞線程;直到event的狀態(tài)為True event.set() #:設(shè)置event的狀態(tài)值為True,所有阻塞池的線程激活進(jìn)入就緒狀態(tài), 等待操作系統(tǒng)調(diào)度; event.clear() #:恢復(fù)event的狀態(tài)值為False?
使用案例:
# 在鏈接mysql服務(wù)器前必須保證mysql已經(jīng)啟動(dòng),而啟動(dòng)需要花費(fèi)一些時(shí)間,所以客戶端不能立即發(fā)起鏈接 需要等待msyql啟動(dòng)完成后立即發(fā)起鏈接 from threading import Event,Thread import timeboot = False def start():global bootprint("正正在啟動(dòng)服務(wù)器.....")time.sleep(5)print("服務(wù)器啟動(dòng)完成!")boot = Truedef connect():while True:if boot:print("鏈接成功")breakelse:print("鏈接失敗")time.sleep(1)Thread(target=start).start() Thread(target=connect).start() Thread(target=connect).start()
?
使用Event改造后:
from threading import Event,Thread import timee = Event() def start():print("正正在啟動(dòng)服務(wù)器.....")time.sleep(3)print("服務(wù)器啟動(dòng)完成!")e.set()def connect():e.wait()print("鏈接成功")Thread(target=start).start() Thread(target=connect).start() Thread(target=connect).start()?
from threading import Event,Thread import timee = Event() def start():global bootprint("正正在啟動(dòng)服務(wù)器.....")time.sleep(5)print("服務(wù)器啟動(dòng)完成!")e.set()def connect():for i in range(1,4):print("第%s次嘗試鏈接" % i)e.wait(1)if e.isSet():print("鏈接成功")breakelse:print("第%s次鏈接失敗" % i)else:print("服務(wù)器未啟動(dòng)!")Thread(target=start).start() Thread(target=connect).start() # Thread(target=connect).start()
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/HZLS/p/10988215.html
總結(jié)
以上是生活随笔為你收集整理的GIL , 线程池 , 同步 , 异步 , 队列 , 事件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: oracle导出一个表数据库,excel
- 下一篇: linux进程号转换成16进制,Shel