日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

GIL , 线程池 , 同步 , 异步 , 队列 , 事件

發(fā)布時間:2023/12/19 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 GIL , 线程池 , 同步 , 异步 , 队列 , 事件 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一.什么是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中,這個全局解釋器鎖,也稱為GIL,是一個互斥鎖,防止多個線程在同一時間執(zhí)行Python字節(jié)碼,這個鎖是非常重要的,
因為CPython的內(nèi)存管理非線程安全的,很多其他的特性依賴于GIL,所以即使它影響了程序效率也無法將其直接去除 總結(jié): 在CPython中,GIL會把線程的并行變成串行,導(dǎo)致效率降低

?

需要知道的是,解釋器并不只有CPython,還有PyPy,JPython等等。GIL也僅存在與CPython中,這并不是Python這門語言的問題,而是CPython解釋器的問題!

?

?

二.GIL帶來的問題

首先必須明確執(zhí)行一個py文件,分為三個步驟

  • 從硬盤加載Python解釋器到內(nèi)存

  • 從硬盤加載py文件到內(nèi)存

  • 解釋器解析py文件內(nèi)容,交給CPU執(zhí)行

  • 其次需要明確的是每當(dāng)執(zhí)行一個py文件,就會立即啟動一個python解釋器,

    當(dāng)執(zhí)行test.py時其內(nèi)存結(jié)構(gòu)如下:

    GIL,叫做全局解釋器鎖,加到了解釋器上,并且是一把互斥鎖,那么這把鎖對應(yīng)用程序到底有什么影響?

    這就需要知道解釋器的作用,以及解釋器與應(yīng)用程序代碼之間的關(guān)系

    py文件中的內(nèi)容本質(zhì)都是字符串,只有在被解釋器解釋時,才具備語法意義,解釋器會將py代碼翻譯為當(dāng)前系統(tǒng)支持的指令交給系統(tǒng)執(zhí)行。

    當(dāng)進(jìn)程中僅存在一條線程時,GIL鎖的存在沒有不會有任何影響,但是如果進(jìn)程中有多個線程時,GIL鎖就開始發(fā)揮作用了。如下圖:

    開啟子線程時,給子線程指定了一個target表示該子線程要處理的任務(wù)即要執(zhí)行的代碼。代碼要執(zhí)行則必須交由解釋器,即多個線程之間就需要共享解釋器,為了避免共享帶來的數(shù)據(jù)競爭問題,于是就給解釋器加上了互斥鎖!

    由于互斥鎖的特性,程序串行,保證數(shù)據(jù)安全,降低執(zhí)行效率,GIL將使得程序整體效率降低!

    ?

    ?

    三.為什么需要GIL

    GIL與GC的孽緣 :

    在使用Python中進(jìn)行編程時,程序員無需參與內(nèi)存的管理工作,這是因為Python有自帶的內(nèi)存管理機(jī)制,簡稱GC。那么GC與GIL有什么關(guān)聯(lián)?

    要搞清楚這個問題,需先了解GC的工作原理,Python中內(nèi)存管理使用的是引用計數(shù),每個數(shù)會被加上一個整型的計數(shù)器,表示這個數(shù)據(jù)被引用的次數(shù),當(dāng)這個整數(shù)變?yōu)?時則表示該數(shù)據(jù)已經(jīng)沒有人使用,成了垃圾數(shù)據(jù)。

    當(dāng)內(nèi)存占用達(dá)到某個閾值時,GC會將其他線程掛起,然后執(zhí)行垃圾清理操作,垃圾清理也是一串代碼,也就需要一條線程來執(zhí)行。

    ?

    示例代碼:

    from threading import Thread def task():a = 10print(a)# 開啟三個子線程執(zhí)行task函數(shù) Thread(target=task).start() Thread(target=task).start() Thread(target=task).start()

    ?

    上述代碼內(nèi)存結(jié)構(gòu)如下:

    通過上圖可以看出,GC與其他線程都在競爭解釋器的執(zhí)行權(quán),而CPU何時切換,以及切換到哪個線程都是無法預(yù)支的,這樣一來就造成了競爭問題 !

    假設(shè)線程1正在定義變量a=10,而定義變量第一步會先到到內(nèi)存中申請空間把10存進(jìn)去,第二步將10的內(nèi)存地址與變量名a進(jìn)行綁定,如果在執(zhí)行完第一步后,CPU切換到了GC線程,GC線程發(fā)現(xiàn)10的地址引用計數(shù)為0則將其當(dāng)成垃圾進(jìn)行了清理,等CPU再次切換到線程1時,剛剛保存的數(shù)據(jù)10已經(jīng)被清理掉了,導(dǎo)致無法正常定義變量。

    當(dāng)然其他一些涉及到內(nèi)存的操作同樣可能產(chǎn)生問題,為了避免GC與其他線程競爭解釋器帶來的問題,CPython簡單粗暴的給解釋器加了互斥鎖

    ?

    如下圖所示:

    有了GIL后,多個線程將不可能在同一時間使用解釋器,從而保證了解釋器的數(shù)據(jù)安全。

    ?

    ?

    GIL的加鎖與解鎖時機(jī)

    加鎖的時機(jī):

      在調(diào)用解釋器時立即加鎖

    解鎖時機(jī):

    • 當(dāng)前線程遇到了IO時釋放

    • 當(dāng)前線程執(zhí)行時間超過設(shè)定值時釋放

    ?

    ?

    但我們并不能因此就否認(rèn)Python這門語言,其原因如下:

  • GIL僅僅在CPython解釋器中存在,在其他的解釋器中沒有,并不是Python這門語言的缺點(diǎn)

  • 在單核處理器下,多線程之間本來就無法真正的并行執(zhí)行

  • 在多核處理下,運(yùn)算效率的確是比單核處理器高,但是要知道現(xiàn)代應(yīng)用程序多數(shù)都是基于網(wǎng)絡(luò)的(qq,微信,爬蟲,瀏覽器等等),CPU的運(yùn)行效率是無法決定網(wǎng)絡(luò)速度的,而網(wǎng)絡(luò)的速度是遠(yuǎn)遠(yuǎn)比不上處理器的運(yùn)算速度,則意味著每次處理器在執(zhí)行運(yùn)算前都需要等待網(wǎng)絡(luò)IO,這樣一來多核優(yōu)勢也就沒有那么明顯了

    舉個例子:

    任務(wù)1 從網(wǎng)絡(luò)上下載一個網(wǎng)頁,等待網(wǎng)絡(luò)IO的時間為1分鐘,解析網(wǎng)頁數(shù)據(jù)花費(fèi),1秒鐘

    任務(wù)2 將用戶輸入數(shù)據(jù)并將其轉(zhuǎn)換為大寫,等待用戶輸入時間為1分鐘,轉(zhuǎn)換為大寫花費(fèi),1秒鐘

    單核CPU下:1.開啟第一個任務(wù)后進(jìn)入等待。2.切換到第二個任務(wù)也進(jìn)入了等待。一分鐘后解析網(wǎng)頁數(shù)據(jù)花費(fèi)1秒解析完成切換到第二個任務(wù),轉(zhuǎn)換為大寫花費(fèi)1秒,那么總耗時為:1分+1秒+1秒 = 1分鐘2秒

    多核CPU下:1.CPU1處理第一個任務(wù)等待1分鐘,解析花費(fèi)1秒鐘。1.CPU2處理第二個任務(wù)等待1分鐘,轉(zhuǎn)換大寫花費(fèi)1秒鐘。由于兩個任務(wù)是并行執(zhí)行的所以總的執(zhí)行時間為1分鐘+1秒鐘 = 1分鐘1秒

    可以發(fā)現(xiàn),多核CPU對于總的執(zhí)行時間提升只有1秒,但是這邊的1秒實(shí)際上是夸張了,轉(zhuǎn)換大寫操作不可能需要1秒,時間非常短!

    上面的兩個任務(wù)都是需要大量IO時間的,這樣的任務(wù)稱之為IO密集型,與之對應(yīng)的是計算密集型即IO操作較少大部分都是計算任務(wù)。

    對于計算密集型任務(wù),Python多線程的確比不上其他語言!為了解決這個弊端,Python推出了多進(jìn)程技術(shù),可以良好的利用多核處理器來完成計算密集任務(wù)。

    總結(jié):

    1.單核下無論是IO密集還是計算密集GIL都不會產(chǎn)生任何影響

    2.多核下對于IO密集任務(wù),GIL會有細(xì)微的影響,基本可以忽略

    3.Cpython中IO密集任務(wù)應(yīng)該采用多線程,計算密集型應(yīng)該采用多進(jìn)程

  • 另外:之所以廣泛采用CPython解釋器,就是因為大量的應(yīng)用程序都是IO密集型的,還有另一個很重要的原因是CPython可以無縫對接各種C語言實(shí)現(xiàn)的庫,這對于一些數(shù)學(xué)計算相關(guān)的應(yīng)用程序而言非常的happy,直接就能使用各種現(xiàn)成的算法

    ?

    計算密集型的效率測試:

    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密集型的效率測試 :

    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ù)的是解釋器級別的數(shù)據(jù)安全,比如對象的引用計數(shù),垃圾分代數(shù)據(jù)等等,具體參考垃圾回收機(jī)制詳解。

    對于程序中自己定義的數(shù)據(jù)則沒有任何的保護(hù)效果,這一點(diǎn)在沒有介紹GIL前我們就已經(jīng)知道了,所以當(dāng)程序中出現(xiàn)了共享自定義的數(shù)據(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)

    ?

    過程分析:

    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

    之所以出現(xiàn)問題是因為兩個線程在并發(fā)的執(zhí)行同一段代碼,解決方案就是加鎖!

    ?

    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)

    ?

    過程分析:

    1.線程1獲得CPU執(zhí)行權(quán),并獲取GIL鎖執(zhí)行代碼 ,得到a的值為0后進(jìn)入睡眠,釋放CPU并釋放GIL,不釋放lock

    2.線程2獲得CPU執(zhí)行權(quán),并獲取GIL鎖,嘗試獲取lock失敗,無法執(zhí)行,釋放CPU并釋放GIL

    3.線程1睡醒后獲得CPU執(zhí)行權(quán),并獲取GIL繼續(xù)執(zhí)行代碼 ,將temp的值0+1后賦給a,執(zhí)行完畢釋放CPU釋放GIL,釋放lock,此時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,此時a的值為2

    ?

    ?

    ?

    ?

    六:進(jìn)程池與線程池

    什么是進(jìn)程/線程池?

    池表示一個容器,本質(zhì)上就是一個存儲進(jìn)程或線程的列表

    ?

    池子中存儲線程還是進(jìn)程?

    如果是IO密集型任務(wù)使用線程池,如果是計算密集任務(wù)則使用進(jìn)程池

    ?

    為什么需要進(jìn)程/線程池?

    在很多情況下需要控制進(jìn)程或線程的數(shù)量在一個合理的范圍,例如TCP程序中,一個客戶端對應(yīng)一個線程,雖然線程的開銷小,但肯定不能無限的開,否則系統(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,此時不會創(chuàng)建進(jìn)程,不指定數(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ù)時立即創(chuàng)建進(jìn)程# 任務(wù)執(zhí)行完成后也不會立即銷毀進(jìn)程time.sleep(2)for i in range(10):pool.submit(task) #再有新任務(wù)是 直接使用之前已經(jīng)創(chuàng)建好的進(jìn)程來執(zhí)行

    ?

    線程池的使用:

    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor from threading import current_thread,active_count import time,os# 創(chuàng)建進(jìn)程池,指定最大線程數(shù)為3,此時不會創(chuàng)建線程,不指定數(shù)量時,默認(rèn)為CPU和核數(shù)*5 pool = ThreadPoolExecutor(3) print(active_count()) # 只有一個主線def task():time.sleep(1)print(current_thread().name,"working..")if __name__ == '__main__':for i in range(10):pool.submit(task) # 第一次提交任務(wù)時立即創(chuàng)建線程# 任務(wù)執(zhí)行完成后也不會立即銷毀time.sleep(2)for i in range(10):pool.submit(task) #再有新任務(wù)時 直接使用之前已經(jīng)創(chuàng)建好的線程來執(zhí)行

    ?

    案例:TCP中的應(yīng)用

    首先要明確,TCP是IO密集型,應(yīng)該使用線程池

    ?

    ?

    ?

    七.同步異步-阻塞非阻塞

    同步異步-阻塞非阻塞,經(jīng)常會被程序員提及,并且概念非常容易混淆!

    ?

    阻塞非阻塞 ------指的是程序的運(yùn)行狀態(tài)

    阻塞:當(dāng)程序執(zhí)行過程中遇到了IO操作,在執(zhí)行IO操作時,程序無法繼續(xù)執(zhí)行其他代碼,稱為阻塞!

    非阻塞:程序在正常運(yùn)行沒有遇到IO操作,或者通過某種方式使程序即使遇到了也不會停在原地,還可以執(zhí)行其他操作,以提高CPU的占用率

    ?

    同步-異步-------- 指的是提交任務(wù)的方式

    同步:指調(diào)用發(fā)起任務(wù)后必須在原地等待任務(wù)執(zhí)行完成,才能繼續(xù)執(zhí)行

    異步:指調(diào)用發(fā)起任務(wù)后不用等待任務(wù)執(zhí)行,可以立即開啟執(zhí)行其他操作

    ?

    同步會有等待的效果但是這和阻塞是完全不同的,阻塞時程序會被剝奪CPU執(zhí)行權(quán),而同步調(diào)用則不會!

    ?

    很明顯異步調(diào)用效率更高,但是任務(wù)的執(zhí)行結(jié)果如何獲取呢?

    ?

    程序中的異步調(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ù)# 會返回一個對象用于表示任務(wù)結(jié)果 objs.append(res_obj)# 該函數(shù)默認(rèn)是阻塞的 會等待池子中所有任務(wù)執(zhí)行結(jié)束后執(zhí)行 pool.shutdown(wait=True)# 從結(jié)果對象中取出執(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) # 會返回一個對象用于表示任務(wù)結(jié)果print(res_obj.result()) #result是同步的一旦調(diào)用就必須等待 任務(wù)執(zhí)行完成拿到結(jié)果 print("over")

    ?

    ?

    ?

    8.異步回調(diào)

    什么是異步回調(diào)

    異步回調(diào)指的是:在發(fā)起一個異步任務(wù)的同時指定一個函數(shù),在異步任務(wù)完成時會自動的調(diào)用這個函數(shù)

    ?

    為什么需要異步回調(diào)

    之前在使用線程池或進(jìn)程池提交任務(wù)時,如果想要處理任務(wù)的執(zhí)行結(jié)果則必須調(diào)用result函數(shù)或是shutdown函數(shù),而它們都是是阻塞的,會等到任務(wù)執(zhí)行完畢后才能繼續(xù)執(zhí)行,這樣一來在這個等待過程中就無法執(zhí)行其他任務(wù),降低了效率,所以需要一種方案,即保證解析結(jié)果的線程不用等待,又能保證數(shù)據(jù)能夠及時被解析,該方案就是異步回調(diào)

    ?

    異步回調(diào)的使用

    先來看一個案例:

    在編寫爬蟲程序時,通常都是兩個步驟:

    1.從服務(wù)器下載一個網(wǎng)頁文件

    2.讀取并且解析文件內(nèi)容,提取有用的數(shù)據(jù)

    按照以上流程可以編寫一個簡單的爬蟲程序

      要請求網(wǎng)頁數(shù)據(jù)則需要使用到第三方的請求庫requests可以通過pip或是pycharm來安裝,在pycharm中點(diǎn)擊settings->解釋器->點(diǎn)擊+號->搜索requests->安裝

    ?

    import requests,re,os,random,time from concurrent.futures import ProcessPoolExecutordef get_data(url):print("%s 正在請求%s" % (os.getpid(),url))time.sleep(random.randint(1,2))response = requests.get(url)print(os.getpid(),"請求成功 數(shù)據(jù)長度",len(response.content))#parser(response) # 3.直接調(diào)用解析方法 哪個進(jìn)程請求完成就那個進(jìn)程解析數(shù)據(jù) 強(qiáng)行使兩個操作耦合到一起了return responsedef parser(obj):data = obj.result()htm = data.content.decode("utf-8")ls = re.findall("href=.*?com",htm)print(os.getpid(),"解析成功",len(ls),"個鏈接")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)致所有請求任務(wù)不能并發(fā)# parser(res) obj = pool.submit(get_data,url) # obj.add_done_callback(parser) # 4.使用異步回調(diào),保證了數(shù)據(jù)可以被及時處理,并且請求和解析解開了耦合# objs.append(obj)# pool.shutdown() # 2.等待所有任務(wù)執(zhí)行結(jié)束在統(tǒng)一的解析# for obj in objs:# res = obj.result()# parser(res)# 1.請求任務(wù)可以并發(fā) 但是結(jié)果不能被及時解析 必須等所有請求完成才能解析# 2.解析任務(wù)變成了串行

    ?

    總結(jié):異步回調(diào)使用方法就是在提交任務(wù)后得到一個Futures對象,調(diào)用對象的add_done_callback來指定一個回調(diào)函數(shù),

    如果把任務(wù)比喻為燒水,沒有回調(diào)時就只能守著水壺等待水開,有了回調(diào)相當(dāng)于換了一個會響的水壺,燒水期間可用作其他的事情,等待水開了水壺會自動發(fā)出聲音,這時候再回來處理。水壺自動發(fā)出聲音就是回調(diào)。

    注意:

  • 使用進(jìn)程池時,回調(diào)函數(shù)都是主進(jìn)程中執(zhí)行執(zhí)行

  • 使用線程池時,回調(diào)函數(shù)的執(zhí)行線程是不確定的,哪個線程空閑就交給哪個線程

  • 回調(diào)函數(shù)默認(rèn)接收一個參數(shù)就是這個任務(wù)對象自己,再通過對象的result函數(shù)來獲取任務(wù)的處理結(jié)果

  • ?

    ?

    ?

    9.線程隊列

    1.Queue 先進(jìn)先出隊列

    與多進(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)先出隊列

    該隊列可以模擬堆棧,實(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)先級隊列

    該隊列可以為每個元素指定一個優(yōu)先級,這個優(yōu)先級可以是數(shù)字,字符串或其他類型,但是必須是可以比較大小的類型,取出數(shù)據(jù)時會按照從小到大的順序取出

    pq = PriorityQueue() # 數(shù)字優(yōu)先級 pq.put((10,"a")) pq.put((11,"a")) pq.put((-11111,"a"))print(pq.get()) print(pq.get()) print(pq.get()) # 字符串優(yōu)先級 pq.put(("b","a")) pq.put(("c","a")) pq.put(("a","a"))print(pq.get()) print(pq.get()) print(pq.get())

    ?

    ?

    10.線程事件Event

    什么是事件

    事件表示在某個時間發(fā)生了某個事情的通知信號,用于線程間協(xié)同工作。

    因為不同線程之間是獨(dú)立運(yùn)行的狀態(tài)不可預(yù)測,所以一個線程與另一個線程間的數(shù)據(jù)是不同步的,當(dāng)一個線程需要利用另一個線程的狀態(tài)來確定自己的下一步操作時,就必須保持線程間數(shù)據(jù)的同步,Event就可以實(shí)現(xiàn)線程間同步

    Event介紹

    Event象包含一個可由線程設(shè)置的信號標(biāo)志,它允許線程等待某些事件的發(fā)生。在 初始情況下,Event對象中的信號標(biāo)志被設(shè)置為假。如果有線程等待一個Event對象, 而這個Event對象的標(biāo)志為假,那么這個線程將會被一直阻塞直至該標(biāo)志為真。一個線程如果將一個Event對象的信號標(biāo)志設(shè)置為真,它將喚醒所有等待這個Event對象的線程。如果一個線程等待一個已經(jīng)被設(shè)置為真的Event對象,那么它將忽略這個事件, 繼續(xù)執(zhí)行

    ?

    可用方法:

    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)啟動,而啟動需要花費(fèi)一些時間,所以客戶端不能立即發(fā)起鏈接 需要等待msyql啟動完成后立即發(fā)起鏈接 from threading import Event,Thread import timeboot = False def start():global bootprint("正正在啟動服務(wù)器.....")time.sleep(5)print("服務(wù)器啟動完成!")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("正正在啟動服務(wù)器.....")time.sleep(3)print("服務(wù)器啟動完成!")e.set()def connect():e.wait()print("鏈接成功")Thread(target=start).start() Thread(target=connect).start() Thread(target=connect).start()

    ?

    增加需求,每次嘗試鏈接等待1秒,嘗試次數(shù)為3次

    from threading import Event,Thread import timee = Event() def start():global bootprint("正正在啟動服務(wù)器.....")time.sleep(5)print("服務(wù)器啟動完成!")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ù)器未啟動!")Thread(target=start).start() Thread(target=connect).start() # Thread(target=connect).start()

    ?

    ?

    ?

    轉(zhuǎn)載于:https://www.cnblogs.com/HZLS/p/10988215.html

    總結(jié)

    以上是生活随笔為你收集整理的GIL , 线程池 , 同步 , 异步 , 队列 , 事件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。