前言:最近轉(zhuǎn)技術(shù)棧,需要學(xué)習(xí)Python的gevent框架濒憋,為了能看懂怎么用DAG圖來優(yōu)化復(fù)雜并有依賴關(guān)系的初始化垫卤。我尋思這不就是Java的CompletableFuture功能嗎斧账,只不過Python對于多線程的支持不太好玻靡,所以才需要引入gevent框架著蟹。
一墩蔓、Python協(xié)程
學(xué)習(xí)gevent之前,就得先了解一下Python原生協(xié)程的支持萧豆,以及它的局限性以及不完善奸披,要不也不需要引入框架嘛。
協(xié)程與線程
線程是操作系統(tǒng)級別的調(diào)用涮雷,線程切換時需要操作系統(tǒng)阵面,保存線程的上下文。
協(xié)程程序級別的調(diào)用,協(xié)程何時需要切換样刷,是由程序員決定的仑扑。協(xié)程上下文切換的開銷更小,而且不需要解決線程安全問題置鼻。
我們可以分別用協(xié)程和線程寫消費者與生產(chǎn)者對比下:
協(xié)程實現(xiàn):
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK'
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()
c = consumer()
produce(c)
線程實現(xiàn)镇饮,參考這篇博客:
https://blog.csdn.net/ldx19980108/article/details/81707751
對比兩種實現(xiàn)方式,可以看出來如果用線程來實現(xiàn)箕母,那么就需要用鎖來保護(hù)共享資源盒让,但是協(xié)程就是函數(shù)執(zhí)行過程中,通過yield來暫停等待其他函數(shù)的輸入司蔬,通過send函數(shù)跑到y(tǒng)ield處執(zhí)行邑茄。開銷要小很多。當(dāng)然Python也有線程俊啼,但是受到GIL(全局解釋器鎖)的局限肺缕,也就是說在線程執(zhí)行的時候,需要獲取GIL鎖授帕,因此退化為單線程了同木,也就是說可以在多個線程中切換,但是不能多個線程同時執(zhí)行跛十,這樣的話只能并發(fā)不能并行彤路,對于多核CPU來說無疑是一種浪費。
Python 協(xié)程的實現(xiàn)
mark 晚點再補 https://zhuanlan.zhihu.com/p/45168167
1芥映、yield/send
2洲尊、select
3、yield from
yield from iterable本質(zhì)上等于for item in iterable: yield item的縮寫版
4奈偏、asyncio
5坞嘀、async/await
二、gevent的使用方式
gevent是用的比較多的Python框架惊来,以協(xié)程為核心丽涩,結(jié)合epoll的多路復(fù)用,解決了GIL鎖的問題裁蚁?矢渊?比起原生的Python來說是封裝的更好?還是存在的優(yōu)化比較多枉证?可以讓python代碼很方便的使用線程?這塊不是很理解矮男。
spawn方法注冊方法到gevent實例上,joinall函數(shù)循環(huán)事件刽严,當(dāng)遇到IO的時候昂灵,會自動切換其他事件。
from gevent import monkey; monkey.patch_all()
import gevent
import requests
from datetime import datetime
def f(url):
print 'time: %s, GET: %s' % (datetime.now(), url)
resp = requests.get(url)
print 'time: %s, %d bytes received from %s.' % (
datetime.now(), len(resp.text), url)
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])
此外舞萄,加鎖也是十分方便的眨补。
# -*- coding: utf-8 -*-
import gevent
from gevent.lock import Semaphore
sem = Semaphore(1)
def f1():
for i in range(5):
sem.acquire()
print 'run f1, this is ', i
sem.release()
gevent.sleep(1)
def f2():
for i in range(5):
sem.acquire()
print 'run f2, that is ', i
sem.release()
gevent.sleep(0.3)
t1 = gevent.spawn(f1)
t2 = gevent.spawn(f2)
gevent.joinall([t1, t2])
三、greenlet的內(nèi)部原理
greenlet是gevent的基礎(chǔ)倒脓,用來實現(xiàn)從協(xié)程到協(xié)程之間的切換撑螺。切換使用的方法是switch。
from greenlet import greenlet
def test1():
print 12
gr2.switch()
print 34
def test2():
print 56
gr1.switch()
print 78
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
用ppt畫了個很丑陋的圖崎弃,為了是將代碼的執(zhí)行過程描述清楚甘晤,greenlet初始化的時候初始化一個空棧,當(dāng)執(zhí)行g(shù)r1.switch時候饲做,會執(zhí)行test1的代碼线婚,打印12,然后當(dāng)前協(xié)程掛起盆均,保存當(dāng)前棧和寄存器塞弊。之后執(zhí)行g(shù)r2.switch切換到另一個棧,執(zhí)行print56泪姨,然后gr1.switch的時候又切回gr1棧游沿,恢復(fù)棧和寄存器,執(zhí)行print34肮砾。
輸出結(jié)果如下所示:
從這個例子來看棧的執(zhí)行似乎诀黍,switch跟函數(shù)調(diào)用很相似。但是switch not call仗处。每個greenlet都有一個parent眯勾,greenlet的創(chuàng)生環(huán)境就是它的Parent,所有的greenlet形成一棵樹婆誓。從下面的例子可以看出來咒精。從test2返回后回到的是main,而不是test1旷档,從這也能看出來他們的區(qū)別模叙。
import greenlet
def test1(x, y):
print id(greenlet.getcurrent()), id(greenlet.getcurrent().parent) # 40240272 40239952
z = gr2.switch(x+y)
print 'back z', z
def test2(u):
print id(greenlet.getcurrent()), id(greenlet.getcurrent().parent) # 40240352 40239952
return 'hehe'
gr1 = greenlet.greenlet(test1)
gr2 = greenlet.greenlet(test2)
print id(greenlet.getcurrent()), id(gr1), id(gr2) # 40239952, 40240272, 40240352
print gr1.switch("hello", " world"), 'back to main' # hehe back to main
四、gevent源碼分析
源碼這塊功底不夠鞋屈,確實有些看不懂了范咨,先mark一下
http://www.reibang.com/p/f55148c41f54