生成器語法
yield ??? 一個對象
??????????????????????返回這個對象
??????????????????????暫停這個函數(shù)
??????????????????????等待下次next重新激活
def my_gen():
print('第一次執(zhí)行')
a = yield 1
print('a is:', a)
print('第二次執(zhí)行')
b = yield 2
print('b is:', b)
print('第三次執(zhí)行')
c = yield 3
print('c is:', c)
g = my_gen()
v1 = next(g)
print(v1)
v2 = next(g)
print(v2)
v3 = next(g)
print(v3)
運行結(jié)果:image.png
send與yield的切換
send 一個對象
??????????????????????激活生成器
??????????????????????執(zhí)行生成器里面的代碼
??????????????????????遇到y(tǒng)ield回到調(diào)用位置
def my_gen():
print('第一次執(zhí)行')
a = yield 1
print('a is:', a)
print('第二次執(zhí)行')
b = yield 2
print('b is:', b)
print('第三次執(zhí)行')
c = yield 3
print('c is:', c)
g = my_gen()
v1 = next(g) #相當(dāng)于g.send(None) 娩贷,生成器的啟動需要next或者send(None)嗜浮,否則報錯
print(v1)
v2 = g.send('cainiao')
print(v2)
v3 = g.send('xiaobai')
print(v3)
運行結(jié)果:image.png
生產(chǎn)者/消費者模型
import random
import time
#消費者
def consumer():
while True:
data = yield
print('消費者拿到:', data)
#生產(chǎn)者
def producer(cos):
next(cos) #激活生成器對象乳愉,讓他執(zhí)行到y(tǒng)ield
while True:
item = random.randint(0, 99) #隨機(jī)生產(chǎn)一個數(shù)
print('生產(chǎn)者生產(chǎn)了:', item)
cos.send(item) #把item給消費者撰糠,切換到消費者函數(shù)
time.sleep(2)
c = consumer() #返回一個生成器對象
producer(c)
運行結(jié)果:image.png
注意事項:
- 對一個生成器必須要先next()讓他執(zhí)行到y(tǒng)ield才能在send數(shù)據(jù)進(jìn)去囱淋。
- 攜程是在一個線程內(nèi)的執(zhí)行的竿秆,本質(zhì)來說就是不同函數(shù)之間的切換調(diào)用成榜。
- 如果某一個協(xié)程被阻塞了,整個線程(進(jìn)程)都被阻塞末早。任意時刻,只有一個協(xié)程在執(zhí)行说庭。
greenlet
- 雖然CPython(標(biāo)準(zhǔn)Python)能夠通過生成器來實現(xiàn)協(xié)程然磷,但使用起來還并不是很方便。
- Python的一個衍生版 Stackless Python刊驴,實現(xiàn)了原生的協(xié)程姿搜,它更利于使用。于是捆憎,大家開始將 Stackless 中關(guān)于協(xié)程的代碼舅柜,單獨拿出來做成了CPython的擴(kuò)展包。
這就是 greenlet 的由來躲惰,greenlet 是底層實現(xiàn)了原生協(xié)程的 C擴(kuò)展庫致份。
需要下載安裝此模塊
pip install greenlet
用greenlet重寫上述代碼:
from greenlet import greenlet
import random
import time
#消費者
def consumer():
while True:
item = p.switch() #切換到生產(chǎn)者,并等待生產(chǎn)者傳入item
print('消費者拿到:', item)
#生產(chǎn)者
def producer():
while True:
item = random.randint(0, 99) #隨機(jī)生產(chǎn)一個數(shù)
print('生產(chǎn)者生產(chǎn)了:', item)
c.switch(item) #將item傳給消費者础拨,切換到消費者函數(shù)
time.sleep(2) #每生產(chǎn)一個就休息一些氮块,方便我們看效果
c = greenlet(consumer) #將一個普通的函數(shù)變成協(xié)程
p = greenlet(producer)
c.switch() #運行消費者
運行結(jié)果:image.png
greenlet作用
- 高性能的原生協(xié)程;
- 語義更加明確的顯式切換诡宗;
- 直接將函數(shù)包裝成協(xié)程滔蝉,保持原有代碼風(fēng)格。
gevent協(xié)程
雖然塔沃,我們有了 基于 epoll 的回調(diào)式編程模式蝠引,但是卻難以使用。即使我們可以通過配合 生成器協(xié)程 進(jìn)行復(fù)雜的封裝蛀柴,以簡化編程難度螃概。但是仍然有一個大的問題: 封裝難度大,現(xiàn)有代碼幾乎完全要重寫gevent名扛,通過封裝了 libev(基于epoll) 和 greenlet 兩個庫谅年。幫我們做好封裝,允許我們以類似于線程的方式使用協(xié)程肮韧。以至于我們幾乎不用重寫原來的代碼就能充分利用 epoll 和 協(xié)程 威力融蹂。
安裝:pip install gevent
import gevent
from gevent import monkey
monkey.patch_socket() #打了這個補(bǔ)丁,下面導(dǎo)入的socket都會被epoll監(jiān)控
import socket
def func(conn):
while True:
data = conn.recv(1024)
if data:
print('接受的數(shù)據(jù)%s'%data.decode())
conn.send(data)
else:
conn.close()
break
server = socket.socket()
server.bind(('0.0.0.0', 7001))
server.listen()
print('等待連接......')
while True:
conn, addr = server.accept()
print('{}上線'.format(addr))
gevent.spawn(func, conn) #創(chuàng)建一個協(xié)程
運行結(jié)果:
image.png
image.png
image.png
gevent的作用
遇到阻塞就切換到另一個協(xié)程繼續(xù)執(zhí)行 弄企!
- 使用基于 epoll 的 libev 來避開阻塞超燃;
- 使用基于 gevent 的 高效協(xié)程 來切換執(zhí)行;
- 只在遇到阻塞的時候切換拘领,沒有輪需的開銷意乓,也沒有線程的開銷;
gevent協(xié)程通信
- 問題一:協(xié)程之間不是能通過switch通信嘛约素?
是的届良,由于 gevent 基于 greenlet笆凌,所以可以。 - 問題二:那為什么還要考慮通信問題士葫?
因為 gevent 不需要我們使用手動切換乞而,而是遇到阻塞就切換,因此我們不會去使用switch 慢显!
image.png
總結(jié):
充分利用cpu多進(jìn)程爪模,用線程來做計算密集型任務(wù),gevent來做IO密集型任務(wù)荚藻。