並發編程--greenlet與gevent


什么是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])

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM