Python協(xié)程操作之gevent(yield阻塞,greenlet),協(xié)程實(shí)現(xiàn)多任務(wù)(有規(guī)律的交替協(xié)作執(zhí)行)用法詳解
本文實(shí)例講述了Python 協(xié)程操作之gevent(yield阻塞,greenlet),協(xié)程實(shí)現(xiàn)多任務(wù)(有規(guī)律的交替協(xié)作執(zhí)行)用法。分享給大家供大家參考,具體如下:
實(shí)現(xiàn)多任務(wù):進(jìn)程消耗的資源最大,線程消耗的資源次之,協(xié)程消耗的資源最少(單線程)。
gevent實(shí)現(xiàn)協(xié)程,gevent是通過(guò)阻塞代碼(例如網(wǎng)絡(luò)延遲等)來(lái)自動(dòng)切換要執(zhí)行的任務(wù),所以在進(jìn)行IO密集型程序時(shí)(例如爬蟲),使用gevent可以提高效率(有效利用網(wǎng)絡(luò)延遲的時(shí)間去執(zhí)行其他任務(wù))。
GIL(全局解釋器鎖)是C語(yǔ)言版本的Python解釋器中專有的,GIL的存在讓多線程的效率變低(哪個(gè)線程搶到鎖,就執(zhí)行哪個(gè)線程)。在IO密集型程序中,多線程依然比單線程效率高(GIL通過(guò)IO阻塞自動(dòng)切換多線程)。
解決GIL(全局解釋器鎖)的問(wèn)題的三種方法:1、不要用C語(yǔ)言版本的Python解釋器。2、讓子線程運(yùn)行其他語(yǔ)言代碼(例如:主線程運(yùn)行Python代碼,子線程運(yùn)行C語(yǔ)言代碼(C語(yǔ)言的動(dòng)態(tài)庫(kù)))。3、多進(jìn)程代替多線程(多進(jìn)程可以利用多核CPU)。
demo.py(協(xié)程底層原理,yield):
import time # 帶yield的函數(shù)并不是函數(shù),而是一個(gè)生成器模板,返回一個(gè)生成器(可用于遍歷迭代) def task_1(): while True: time.sleep(0.1) yield # 阻塞,等待next迭代解阻塞 def task_2(): while True: time.sleep(0.1) yield def main(): t1 = task_1() # 返回的t1是一個(gè)生成器。 此時(shí)并未執(zhí)行task_1中的代碼 t2 = task_2() # 先讓t1運(yùn)行一會(huì),當(dāng)t1中遇到y(tǒng)ield的時(shí)候,再返回到24行,然后 # 執(zhí)行t2,當(dāng)它遇到y(tǒng)ield的時(shí)候,再次切換到t1中 # 這樣t1/t2/t1/t2的交替運(yùn)行,最終實(shí)現(xiàn)了多任務(wù)....協(xié)程 while True: next(t1) # 執(zhí)行task_1中的代碼,遇到y(tǒng)ield阻塞task_1。 等待下一次next激活。 next(t2) # 實(shí)現(xiàn)task_1與task_2交替執(zhí)行,實(shí)現(xiàn)多任務(wù)...協(xié)程 if __name__ == "__main__": main()
demo.py(greenlet實(shí)現(xiàn)協(xié)程,封裝了yield):
from greenlet import greenlet # 需要安裝greenlet模塊 sudo pip3 install greenlet (python2.x使用pip) import time def test1(): while True: print("---A--") gr2.switch() # 切換到gr2中的任務(wù)。 time.sleep(0.5) def test2(): while True: print("---B--") gr1.switch() # 切換到gr1中的任務(wù)。 time.sleep(0.5) gr1 = greenlet(test1) # greenlet 底層封裝了yield。 gr2 = greenlet(test2) #切換到gr1中運(yùn)行 gr1.switch()
demo.py(gevent實(shí)現(xiàn)協(xié)程,封裝了greenlet,遇到阻塞代碼自動(dòng)切換協(xié)程任務(wù)):
import gevent # 需要安裝gevent模塊 sudo pip3 install gevent (python2.x使用pip) import time def f1(n): for i in range(n): print(gevent.getcurrent(), i) gevent.sleep(0.5) # 為了提高協(xié)程效率,遇到阻塞類代碼,會(huì)自動(dòng)切換協(xié)程任務(wù)。 # time.sleep(0.5) # 阻塞類代碼必須使用gevent自己包裝的代碼,原生阻塞類代碼不會(huì)切換協(xié)程任務(wù)。 # 可以使用monkey.patch_all()將所有原生阻塞類代碼替換成gevent包裝的阻塞類代碼。 def f2(n): for i in range(n): print(gevent.getcurrent(), i) # <Greenlet "Greenlet-0" at 0x7f4a09b34648: f1(5)> 0 gevent.sleep(0.5) # time.sleep(0.5) def f3(n): for i in range(n): print(gevent.getcurrent(), i) gevent.sleep(0.5) # time.sleep(0.5) g1 = gevent.spawn(f1, 5) # gevent其實(shí)是對(duì)greenlet的封裝。 g2 = gevent.spawn(f2, 5) # 第一個(gè)參數(shù)f2表示協(xié)程執(zhí)行的具體任務(wù)(函數(shù)),第二個(gè)參數(shù)5表示要傳給f2的參數(shù) g3 = gevent.spawn(f3, 5) g1.join() # 遇到阻塞類代碼,自動(dòng)切換協(xié)程任務(wù)。 g2.join() g3.join()
運(yùn)行結(jié)果:
<Greenlet at 0x1985030: f1(5)> 0
<Greenlet at 0x1b431c8: f2(5)> 0
<Greenlet at 0x1b43140: f3(5)> 0
<Greenlet at 0x1985030: f1(5)> 1
<Greenlet at 0x1b431c8: f2(5)> 1
<Greenlet at 0x1b43140: f3(5)> 1
<Greenlet at 0x1985030: f1(5)> 2
<Greenlet at 0x1b431c8: f2(5)> 2
<Greenlet at 0x1b43140: f3(5)> 2
<Greenlet at 0x1985030: f1(5)> 3
<Greenlet at 0x1b431c8: f2(5)> 3
<Greenlet at 0x1b43140: f3(5)> 3
<Greenlet at 0x1985030: f1(5)> 4
<Greenlet at 0x1b431c8: f2(5)> 4
<Greenlet at 0x1b43140: f3(5)> 4
demo.py(gevent打補(bǔ)丁,monkey自動(dòng)替換原生阻塞類代碼。重要,常用):
import gevent # 需要安裝gevent模塊 sudo pip3 install gevent (python2.x使用pip) import time from gevent import monkey # gevent打補(bǔ)丁 monkey.patch_all() # 將所有原生阻塞類代碼自動(dòng)替換成gevent包裝的阻塞類代碼。 def f1(n): for i in range(n): print(gevent.getcurrent(), i) time.sleep(0.5) # 會(huì)自動(dòng)替換成 gevent.sleep(0.5) def f2(n): for i in range(n): print(gevent.getcurrent(), i) time.sleep(0.5) def f3(n): for i in range(n): print(gevent.getcurrent(), i) time.sleep(0.5) # g1 = gevent.spawn(f1, 5) # g2 = gevent.spawn(f2, 5) # g3 = gevent.spawn(f3, 5) # g1.join() # g2.join() # g3.join() # 一種簡(jiǎn)便寫法 gevent.joinall([ gevent.spawn(f1, 5), gevent.spawn(f2, 5), gevent.spawn(f3, 5) ])
運(yùn)行結(jié)果:
<Greenlet at 0x22e0938: f1(5)> 0
<Greenlet at 0x22e09c0: f2(5)> 0
<Greenlet at 0x22e0a48: f3(5)> 0
<Greenlet at 0x22e0938: f1(5)> 1
<Greenlet at 0x22e09c0: f2(5)> 1
<Greenlet at 0x22e0a48: f3(5)> 1
<Greenlet at 0x22e0938: f1(5)> 2
<Greenlet at 0x22e09c0: f2(5)> 2
<Greenlet at 0x22e0a48: f3(5)> 2
<Greenlet at 0x22e0938: f1(5)> 3
<Greenlet at 0x22e09c0: f2(5)> 3
<Greenlet at 0x22e0a48: f3(5)> 3
<Greenlet at 0x22e0938: f1(5)> 4
<Greenlet at 0x22e09c0: f2(5)> 4
<Greenlet at 0x22e0a48: f3(5)> 4
demo.py(gevent底層原理):
import socket import time tcp_server_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp_server_tcp.bind(("", 7899)) tcp_server_tcp.listen(128) tcp_server_tcp.setblocking(False) # 設(shè)置套接字為非堵塞的方式。 (接收數(shù)據(jù)時(shí)如果沒(méi)有接到數(shù)據(jù)(阻塞)那么就拋異常,否則正常接收數(shù)據(jù)。) client_socket_list = list() # 用于保存與客戶端連接的套接字。 while True: # time.sleep(0.5) try: new_socket, new_addr = tcp_server_tcp.accept() # 用拋異常的方式代替阻塞。 except Exception as ret: print("---沒(méi)有新的客戶端到來(lái)---") else: print("---只要沒(méi)有產(chǎn)生異常,那么也就意味著 來(lái)了一個(gè)新的客戶端----") new_socket.setblocking(False) # 設(shè)置套接字為非堵塞的方式。 (如果需要阻塞就直接拋異常代替阻塞) client_socket_list.append(new_socket) for client_socket in client_socket_list: try: recv_data = client_socket.recv(1024) # 用拋異常的方式代替阻塞。 except Exception as ret: print("----這個(gè)客戶端還沒(méi)有發(fā)送過(guò)來(lái)數(shù)據(jù)----") else: if recv_data: # 對(duì)方發(fā)送過(guò)來(lái)數(shù)據(jù) print("----客戶端發(fā)送過(guò)來(lái)了數(shù)據(jù)-----") else: # 對(duì)方調(diào)用close 導(dǎo)致了 recv返回 client_socket.close() client_socket_list.remove(client_socket) print("---客戶端已經(jīng)關(guān)閉----")
更多關(guān)于Python相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Python進(jìn)程與線程操作技巧總結(jié)》、《Python數(shù)據(jù)結(jié)構(gòu)與算法教程》、《Python函數(shù)使用技巧總結(jié)》、《Python字符串操作技巧匯總》、《Python入門與進(jìn)階經(jīng)典教程》、《Python+MySQL數(shù)據(jù)庫(kù)程序設(shè)計(jì)入門教程》及《Python常見數(shù)據(jù)庫(kù)操作技巧匯總》
希望本文所述對(duì)大家Python程序設(shè)計(jì)有所幫助。
相關(guān)文章
Python實(shí)現(xiàn)設(shè)置windows桌面壁紙代碼分享
這篇文章主要介紹了Python實(shí)現(xiàn)設(shè)置windows桌面壁紙,本文直接給出實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-03-03django自帶的server 讓外網(wǎng)主機(jī)訪問(wèn)方法
今天小編就為大家分享一篇django自帶的server 讓外網(wǎng)主機(jī)訪問(wèn)方法。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05Python算法之棧(stack)的實(shí)現(xiàn)
這篇文章主要介紹了Python算法之棧(stack)的實(shí)現(xiàn),非常實(shí)用,需要的朋友可以參考下2014-08-08openCV中值濾波和均值濾波的代碼實(shí)現(xiàn)
在我們生活中的有很多時(shí)候都可以用到濾波,例如美顏的磨皮功能,本文就詳細(xì)的介紹了openCV中值濾波和均值濾波的代碼實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03深入了解Python枚舉類型的相關(guān)知識(shí)
這篇文章主要介紹了深入了解Python枚舉類型的相關(guān)知識(shí),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07對(duì)Python實(shí)現(xiàn)累加函數(shù)的方法詳解
今天小編就為大家分享一篇對(duì)Python實(shí)現(xiàn)累加函數(shù)的方法詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-01-01