什么是greenlet?
雖然CPython(標准Python)能夠通過生成器來實現協程,但使用起來還並不是很方便。
與此同時,Python的一個衍生版 Stackless Python實現了原生的協程,它更利於使用。
於是,大家開始將 Stackless 中關於協程的代碼單獨拿出來做成了CPython的擴展包。
這就是 greenlet 的由來,因此 greenlet 是底層實現了原生協程的 C擴展庫
greenlet的基本使用
# 基於greenlet的生產者消費者協程 from greenlet import greenlet import random import time def Producer(): while True: item = random.randint(1, 10) print("生產<{}>中...".format(item)) time.sleep(1) c.switch(item) # 切換到消費者,並將item傳入。 def Consumer(): while True: item = p.switch() # 切換到生產者。等待生產者傳遞參數item print("消費<{}>中..".format(item)) c = greenlet(Consumer) # 將普通函數編程協程 p = greenlet(Producer) # 同理 c.switch() # 啟動協程,Consumer先執行 """ 從consumer開始執行,執行到item=p.switch()時,程序切換到producer,並等待傳參 producer得到執行權后,生成一個item,並往下執行代碼 當producer執行到c.switch(item)時,程序攜帶傳遞的item切換到consumer, consumer繼續往下執行,直到下一次運行到p.switch時,交出執行權,切換到producer,重復以上過程 greenlet的價值在於高性能的原生協程, 且語義更加明確、顯示切換 執行到switch時就切換程序 直接將函數包裝成協程,可以保留原代碼的風格 """
什么是gevent?
雖然,我們有了 基於 epoll 的回調式編程模式,但是卻難以使用。即使我們可以通過配合 生成器協程 進行復雜的封裝,以簡化編程難度。
但是仍然有一個大的問題: 封裝難度大,現有代碼幾乎完全要重寫gevent,通過封裝了 libev(基於epoll) 和 greenlet 兩個庫。
幫我們做好封裝,允許我們以類似於線程的方式使用協程。以至於我們幾乎不用重寫原來的代碼就能充分利用 epoll 和 協程 威力。
gevent的常用操作
""" gevent: 通過greenlet實現協程,核心就是遇到IO操作,會自動切換到其他協程 """ # 將python標准庫中的一些阻塞操作變為非阻塞 from gevent import monkey;monkey.patch_all() # 使用猴子補丁要寫在第一行 import gevent def test1(): print("test1") gevent.sleep(0) # 模擬耗時操作 print("test11") def test2(): print("test2") gevent.sleep(0) # 模擬耗時操作 print("test22") # g1 = gevent.spawn(test1) # 將函數封裝成協程,並啟動 # g2 = gevent.spawn(test2) # gevent.joinall([g1, g2]) """ # joinall() 阻塞當前流程,執行給定的greenlet(列表中的對象),等待程序執行完 # spawn是啟動協程,參數為函數名及其參數 運行結果: test1 test2 test11 test22 代碼執行test1,打印test1,遇到gevent.sleep(0)時切換程序,執行test2 test()執行,打印test2,執行到gevent.sleep(0)時切換程序 執行test1在gevent.sleep(0)后面的代碼,直到再次遇到gevent時,切換程序 然后在test2中,繼續執行gevent后的代碼,直到遇到gevent時,再次切換 直到程序執行完畢 gevent的價值在於它的使用基於epoll的libev來避開阻塞; 使用基於gevent的高效協程,來切換執行 只在遇到阻塞的時候切換,沒有輪詢和線程開銷 """
基於gevent的並發服務器
# 基於gevent的並發服務器實現 import gevent # 將python內置的socket換成封裝了IO多路復用的socket from gevent import monkey;monkey.patch_all() import socket # 實例化socket server = socket.socket() # 綁定ip和端口 server.bind(('0.0.0.0', 8000)) # 綁定監聽數量 server.listen(1000) def worker(connection): """ 協程需要處理的事情 :param connection: :return: """ while True: recv_data = connection.recv(1024) # 等待接收數據 if recv_data: print(recv_data) connection.send(recv_data) # 將接收的數據原路返回 else: connection.close() # 發送完畢斷開 break while True: conn, addr = server.accept() # 等待客戶端連接,遇到阻塞切換 gevent.spawn(worker, conn) # 生成協程,並將conn作為參數傳入
gevent的通信
# gevent通信 import time import random from gevent import monkey;monkey.patch_all() import gevent from gevent.queue import Queue def write(q): while True: print("put:{}".format('text')) q.put('text') gevent.sleep(0) # 模擬阻塞 def read(q): while True: print("get:{}".format(q.get())) # get本身是阻塞 # q = Queue() # w = gevent.spawn(write, q) # 遇到阻塞自動切換 # r = gevent.spawn(read, q) # gevent.joinall([w, r])