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

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

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

编程问答

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

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

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

?

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

?

?

二.GIL帶來(lái)的問(wèn)題

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

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

  • 從硬盤(pán)加載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)程中僅存在一條線(xiàn)程時(shí),GIL鎖的存在沒(méi)有不會(huì)有任何影響,但是如果進(jìn)程中有多個(gè)線(xiàn)程時(shí),GIL鎖就開(kāi)始發(fā)揮作用了。如下圖:

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

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

    ?

    ?

    三.為什么需要GIL

    GIL與GC的孽緣 :

    在使用Python中進(jìn)行編程時(shí),程序員無(wú)需參與內(nèi)存的管理工作,這是因?yàn)镻ython有自帶的內(nèi)存管理機(jī)制,簡(jiǎn)稱(chēng)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ì)將其他線(xiàn)程掛起,然后執(zhí)行垃圾清理操作,垃圾清理也是一串代碼,也就需要一條線(xiàn)程來(lái)執(zhí)行。

    ?

    示例代碼:

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

    ?

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

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

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

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

    ?

    如下圖所示:

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

    ?

    ?

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

    加鎖的時(shí)機(jī):

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

    解鎖時(shí)機(jī):

    • 當(dāng)前線(xiàn)程遇到了IO時(shí)釋放

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

    ?

    ?

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

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

  • 在單核處理器下,多線(xiàn)程之間本來(lái)就無(wú)法真正的并行執(zhí)行

  • 在多核處理下,運(yùn)算效率的確是比單核處理器高,但是要知道現(xiàn)代應(yīng)用程序多數(shù)都是基于網(wǎng)絡(luò)的(qq,微信,爬蟲(chóng),瀏覽器等等),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 將用戶(hù)輸入數(shù)據(jù)并將其轉(zhuǎn)換為大寫(xiě),等待用戶(hù)輸入時(shí)間為1分鐘,轉(zhuǎn)換為大寫(xiě)花費(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)換為大寫(xiě)花費(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)換大寫(xiě)花費(fèi)1秒鐘。由于兩個(gè)任務(wù)是并行執(zhí)行的所以總的執(zhí)行時(shí)間為1分鐘+1秒鐘 = 1分鐘1秒

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

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

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

    總結(jié):

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

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

    3.Cpython中IO密集任務(wù)應(yīng)該采用多線(xiàn)程,計(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)# 多線(xiàn)程# 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)# 多線(xiàn)程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)

    ?

    ?

    五.自定義的線(xiàn)程鎖與GIL的區(qū)別

    GIL保護(hù)的是解釋器級(jí)別的數(shù)據(jù)安全,比如對(duì)象的引用計(jì)數(shù),垃圾分代數(shù)據(jù)等等,具體參考垃圾回收機(jī)制詳解。

    對(duì)于程序中自己定義的數(shù)據(jù)則沒(méi)有任何的保護(hù)效果,這一點(diǎn)在沒(méi)有介紹GIL前我們就已經(jīng)知道了,所以當(dāng)程序中出現(xiàn)了共享自定義的數(shù)據(jù)時(shí)就要自己加鎖

    如下例:

    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.線(xiàn)程1獲得CPU執(zhí)行權(quán),并獲取GIL鎖執(zhí)行代碼 ,得到a的值為0后進(jìn)入睡眠,釋放CPU并釋放GIL

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

    3.線(xiàn)程1睡醒后獲得CPU執(zhí)行權(quán),并獲取GIL執(zhí)行代碼 ,將temp的值0+1后賦給a,執(zhí)行完畢釋放CPU并釋放GIL

    4.線(xiàn)程2睡醒后獲得CPU執(zhí)行權(quán),并獲取GIL執(zhí)行代碼 ,將temp的值0+1后賦給a,執(zhí)行完畢釋放CPU并釋放GIL,最后a的值也就是1

    之所以出現(xiàn)問(wèn)題是因?yàn)閮蓚€(gè)線(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)

    ?

    過(guò)程分析:

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

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

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

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

    5.線(xiàn)程2睡醒后獲得CPU執(zhí)行權(quán),獲取GIL繼續(xù)執(zhí)行代碼 ,將temp的值1+1后賦給a,執(zhí)行完畢釋放CPU釋放GIL,釋放lock,此時(shí)a的值為2

    ?

    ?

    ?

    ?

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

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

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

    ?

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

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

    ?

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

    在很多情況下需要控制進(jìn)程或線(xiàn)程的數(shù)量在一個(gè)合理的范圍,例如TCP程序中,一個(gè)客戶(hù)端對(duì)應(yīng)一個(gè)線(xiàn)程,雖然線(xiàn)程的開(kāi)銷(xiāo)小,但肯定不能無(wú)限的開(kāi),否則系統(tǒng)資源遲早被耗盡,解決的辦法就是控制線(xiàn)程的數(shù)量。

    線(xiàn)程/進(jìn)程池不僅幫我們控制線(xiàn)程/進(jìn)程的數(shù)量,還幫我們完成了線(xiàn)程/進(jìn)程的創(chuàng)建,銷(xiāo)毀,以及任務(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ì)立即銷(xiāo)毀進(jìn)程time.sleep(2)for i in range(10):pool.submit(task) #再有新任務(wù)是 直接使用之前已經(jīng)創(chuàng)建好的進(jìn)程來(lái)執(zhí)行

    ?

    線(xiàn)程池的使用:

    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor from threading import current_thread,active_count import time,os# 創(chuàng)建進(jìn)程池,指定最大線(xiàn)程數(shù)為3,此時(shí)不會(huì)創(chuàng)建線(xiàn)程,不指定數(shù)量時(shí),默認(rèn)為CPU和核數(shù)*5 pool = ThreadPoolExecutor(3) print(active_count()) # 只有一個(gè)主線(xiàn)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)建線(xiàn)程# 任務(wù)執(zhí)行完成后也不會(huì)立即銷(xiāo)毀time.sleep(2)for i in range(10):pool.submit(task) #再有新任務(wù)時(shí) 直接使用之前已經(jīng)創(chuàng)建好的線(xiàn)程來(lái)執(zhí)行

    ?

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

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

    ?

    ?

    ?

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

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

    ?

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

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

    非阻塞:程序在正常運(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)用效率更高,但是任務(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ù)# 會(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)

    之前在使用線(xiàn)程池或進(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é)果的線(xiàn)程不用等待,又能保證數(shù)據(jù)能夠及時(shí)被解析,該方案就是異步回調(diào)

    ?

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

    先來(lái)看一個(gè)案例:

    在編寫(xiě)爬蟲(chóng)程序時(shí),通常都是兩個(gè)步驟:

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

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

    按照以上流程可以編寫(xiě)一個(gè)簡(jiǎn)單的爬蟲(chóng)程序

      要請(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í)行

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

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

  • ?

    ?

    ?

    9.線(xiàn)程隊(duì)列

    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ù)字,字符串或其他類(lèi)型,但是必須是可以比較大小的類(lèi)型,取出數(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.線(xiàn)程事件Event

    什么是事件

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

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

    Event介紹

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

    ?

    可用方法:

    event.isSet()   #:返回event的狀態(tài)值; event.wait()   #:將阻塞線(xiàn)程;直到event的狀態(tài)為T(mén)rue event.set()    #:設(shè)置event的狀態(tài)值為T(mén)rue,所有阻塞池的線(xiàn)程激活進(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í)間,所以客戶(hù)端不能立即發(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()

    ?

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

    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)題。

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