一翠忠、協(xié)程
協(xié)程,又稱微線程乞榨,纖程秽之。英文名Coroutine。
協(xié)程不是進(jìn)程吃既,也不是線程考榨,它就是一個函數(shù),一個特殊的函數(shù)——可以在某個地方掛起鹦倚,并且可以重新在掛起處繼續(xù)運(yùn)行河质。所以說,協(xié)程與進(jìn)程震叙、線程相比愤诱,不是一個維度的概念。
一個進(jìn)程可以包含多個線程捐友,一個線程也可以包含多個協(xié)程淫半,也就是說,一個線程內(nèi)可以有多個那樣的特殊函數(shù)在運(yùn)行匣砖。但是有一點(diǎn)科吭,必須明確,一個線程內(nèi)的多個協(xié)程的運(yùn)行是串行的猴鲫。如果有多核CPU的話对人,多個進(jìn)程或一個進(jìn)程內(nèi)的多個線程是可以并行運(yùn)行的,但是一個線程內(nèi)的多個協(xié)程卻絕對串行的拂共,無論有多少個CPU(核)牺弄。這個比較好理解,畢竟協(xié)程雖然是一個特殊的函數(shù)宜狐,但仍然是一個函數(shù)势告。一個線程內(nèi)可以運(yùn)行多個函數(shù)蛇捌,但是這些函數(shù)都是串行運(yùn)行的。當(dāng)一個協(xié)程運(yùn)行時咱台,其他協(xié)程必須掛起络拌。
通俗的理解:在一個線程中的某個函數(shù),可以在任何地方保存當(dāng)前函數(shù)的一些臨時變量等信息回溺,然后切換到另外一個函數(shù)中執(zhí)行春贸,注意不是通過調(diào)用函數(shù)的方式做到的,并且切換的次數(shù)以及什么時候再切換到原來的函數(shù)都由開發(fā)者自己確定
學(xué)習(xí)Python中的小伙伴遗遵,需要學(xué)習(xí)資料的話萍恕,可以到我的微信公眾號:Python學(xué)習(xí)知識圈,后臺回復(fù):“01”车要,即可拿Python學(xué)習(xí)資料
二允粤、yield實(shí)現(xiàn)協(xié)程
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> 1 import time
2
3 def A():
4 while True:
5 print("----A---")
6 yield
7 time.sleep(0.3)
8
9 def B(c):
10 while True:
11 print("----B---")
12 next(c)
13 time.sleep(0.3)
14
15 if name=='main':
16 a = A()
17 B(a)
</pre>
執(zhí)行結(jié)果
<pre class="prettyprint hljs lua" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">----B---
----A---
----B---
----A---
----B---
----A---
----B---
----A---
----B---
----A---
省略。屯蹦。维哈。
</pre>
代碼說明:
第17行:調(diào)用函數(shù)B,并把a(bǔ)傳遞進(jìn)去登澜。執(zhí)行打印B的代碼阔挠,代碼執(zhí)行到next(c)時,會調(diào)用函數(shù)A脑蠕,執(zhí)行打印A的代碼购撼,當(dāng)代碼實(shí)行帶第6行遇到y(tǒng)ield的實(shí)行,該協(xié)程進(jìn)入等待狀態(tài)谴仙,回到原來next(c)處繼續(xù)執(zhí)行迂求, 從而實(shí)現(xiàn)多協(xié)程的切換,通過yield關(guān)鍵字晃跺。
三揩局、greenlet
1、greenlet實(shí)現(xiàn)多任務(wù)協(xié)程
為了更好使用協(xié)程來完成多任務(wù)掀虎,python中的greenlet模塊對其封裝凌盯,從而使得切換任務(wù)變的更加簡單,在使用前先要確保greenlet模塊安裝
使用如下命令安裝greenlet模塊:
<pre class="hljs nginx" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">sudo pip install greenlet
</pre>
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#coding = utf-8
from greenlet import greenlet
def test1():
print("1")
gr2.switch()
print("2")
def test2():
print("3")
gr1.switch()
print("4")
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
</pre>
運(yùn)行結(jié)果:
當(dāng)創(chuàng)建一個greenlet時烹玉,首先初始化一個空的棧驰怎, switch到這個棧的時候,會運(yùn)行在greenlet構(gòu)造時傳入的函數(shù)(首先在test1中打印 1)二打, 如果在這個函數(shù)(test1)中switch到其他協(xié)程(到了test2 打印3)县忌,那么該協(xié)程會被掛起,等到切換回來(在test1切換回來 打印2)。當(dāng)這個協(xié)程對應(yīng)函數(shù)執(zhí)行完畢症杏,那么這個協(xié)程就變成dead狀態(tài)装获。
注意 上面沒有打印test2的最后一行輸出 4,因為在test2中切換到gr1之后掛起鸳慈,但是沒有地方再切換回來饱溢。
2喧伞、greenlet的模塊與類
我們首先看一下greenlet這個module里面的屬性
<pre class="prettyprint hljs ruby" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">>>> import greenlet
dir(greenlet)
['GREENLET_USE_GC', 'GREENLET_USE_TRACING', 'GreenletExit', '_C_API', 'doc', 'file', 'loader', 'name', 'package', 'spec', 'version', 'error', 'getcurrent', 'gettrace', 'greenlet', 'settrace']
</pre>
其中走芋,比較重要的是getcurrent(), 類greenlet潘鲫、異常類GreenletExit翁逞。
getcurrent()返回當(dāng)前的greenlet實(shí)例;
GreenletExit:是一個特殊的異常溉仑,當(dāng)觸發(fā)了這個異常的時候挖函,即使不處理,也不會拋到其parent(后面會提到協(xié)程中對返回值或者異常的處理)
然后我們再來看看greenlet.greenlet這個類:
<pre class="prettyprint hljs ruby" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">>>>dir(greenlet.greenlet)
['GreenletExit', 'bool', 'class', 'delattr', 'dict', 'dir', 'doc', 'eq', 'format', 'ge', 'getattribute', 'getstate', 'gt', 'hash', 'init', 'init_subclass', 'le', 'lt', 'ne', 'new', 'reduce', 'reduce_ex', 'repr', 'setattr', 'sizeof', 'str', 'subclasshook', '_stack_saved', 'dead', 'error', 'getcurrent', 'gettrace', 'gr_frame', 'parent', 'run', 'settrace', 'switch', 'throw']
</pre>
比較重要的幾個屬性:
run:當(dāng)greenlet啟動的時候會調(diào)用到這個callable浊竟,如果我們需要繼承g(shù)reenlet.greenlet時怨喘,需要重寫該方法
switch:前面已經(jīng)介紹過了,在greenlet之間切換
parent:可讀寫屬性振定,后面介紹
dead:如果greenlet執(zhí)行結(jié)束必怜,那么該屬性為true
throw:切換到指定greenlet后立即跑出異常
文章后面提到的greenlet大多都是指greenlet.greenlet這個class,請注意區(qū)別
對于greenlet后频,最常用的寫法是 x = gr.switch(y)梳庆。 這句話的意思是切換到gr,傳入?yún)?shù)y卑惜。當(dāng)從其他協(xié)程( 不一定是這個gr )切換回來的時候膏执,將值付給x。
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import greenlet
def test1(x, y):
z = gr2.switch(x + y)
print("test1:%s" % z)
def test2(a):
print('test2:%s' % a)
gr1.switch(10)
gr1 = greenlet.greenlet(test1)
gr2 = greenlet.greenlet(test2)
print(gr1.switch("Hello", "World"))
</pre>
運(yùn)行結(jié)果為:
<pre class="hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">test2:HelloWorld
test1:10
None
</pre>
上面的例子露久,第10行從main greenlet切換到了gr1更米,test1第3行切換到了gs2,然后gr1掛起毫痕,第7行從gr2切回gr1時征峦,將值(10)返回值給了 z。
3镇草、greenlet生命周期
文章開始的地方提到第一個例子中的gr2其實(shí)并沒有正常結(jié)束眶痰,我們可以借用greenlet.dead這個屬性來查看
運(yùn)行結(jié)果為:
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> 1 import greenlet
2
3
4 def test1():
5 gr2.switch(1)
6 print("test1: finished")
7
8
9 def test2(x):
10 print("test2:first %s" % x)
11 gr1.switch()
12 print("test2:back")
13
14 gr1 = greenlet.greenlet(test1)
15 gr2 = greenlet.greenlet(test2)
16 gr1.switch()
17 print("gr1 is dead? : %s, gr2 is dead? :%s" % (gr1.dead, gr2.dead))
18 gr2.switch()
19 print("gr1 is dead? : %s, gr2 is dead? :%s" % (gr1.dead, gr2.dead))
</pre>
運(yùn)行結(jié)果為:
<pre class="prettyprint hljs vbscript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">test2:first 1
test1: finished
gr1 is dead? : True, gr2 is dead? :False
test2:back
gr1 is dead? : True, gr2 is dead? :True
</pre>
只有當(dāng)協(xié) 程對應(yīng)的函數(shù)執(zhí)行完畢,協(xié)程才會die 梯啤,所以第一次Check的時候gr2并沒有die竖伯,因為第12行切換出去了就沒切回來。在main中再switch到gr2的時候, 執(zhí)行后面的邏輯七婴,gr2 die
4祟偷、greenlet注意事項
使用greenlet需要注意一下三點(diǎn):
第一:greenlet創(chuàng)生之后,一定要結(jié)束打厘,不能switch出去就不回來了修肠,否則容易造成內(nèi)存泄露
第二:python中每個線程都有自己的main greenlet及其對應(yīng)的sub-greenlet ,不能線程之間的greenlet是不能相互切換的
第三:不能存在循環(huán)引用户盯,這個是官方文檔明確說明
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> 1 from greenlet import greenlet, GreenletExit
2 huge = []
3 def show_leak():
4 def test1():
5 gr2.switch()
6
7 def test2():
8 huge.extend([x* x for x in range(100)])
9 gr1.switch()
10 print 'finish switch del huge'
11 del huge[:]
12
13 gr1 = greenlet(test1)
14 gr2 = greenlet(test2)
15 gr1.switch()
16 gr1 = gr2 = None
17 print 'length of huge is zero ? %s' % len(huge)
18
19 if name == 'main':
20 show_leak()
</pre>
在test2函數(shù)中 第11行嵌施,我們將huge清空,然后再第16行將gr1莽鸭、gr2的引用計數(shù)降到了0吗伤。但運(yùn)行結(jié)果告訴我們,第11行并沒有執(zhí)行硫眨,所以如果一個協(xié)程沒有正常結(jié)束是很危險的足淆,往往不符合程序員的預(yù)期。greenlet提供了解決這個問題的辦法礁阁,官網(wǎng)文檔提到:如果一個greenlet實(shí)例的引用計數(shù)變成0巧号,那么會在上次掛起的地方拋出GreenletExit異常,這就使得我們可以通過try ... finally 處理資源泄露的情況姥闭。如下面的代碼:
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">1 from greenlet import greenlet, GreenletExit
2 huge = []
3 def show_leak():
4 def test1():
5 gr2.switch()
6
7 def test2():
8 huge.extend([x* x for x in range(100)])
9 try:
10 gr1.switch()
11 finally:
12 print 'finish switch del huge'
13 del huge[:]
14
15 gr1 = greenlet(test1)
16 gr2 = greenlet(test2)
17 gr1.switch()
18 gr1 = gr2 = None
19 print 'length of huge is zero ? %s' % len(huge)
20
21 if name == 'main':
22 show_leak()
</pre>
上述代碼的switch流程:main greenlet --> gr1 --> gr2 --> gr1 --> main greenlet, 很明顯gr2沒有正常結(jié)束(在第10行刮起了)丹鸿。第18行之后gr1,gr2的引用計數(shù)都變成0,那么會在第10行拋出GreenletExit異常泣栈,因此finally語句有機(jī)會執(zhí)行卜高。同時,在文章開始介紹Greenlet module的時候也提到了南片,GreenletExit這個異常并不會拋出到parent掺涛,所以main greenlet也不會出異常。
四疼进、gevent
greenlet已經(jīng)實(shí)現(xiàn)了協(xié)程薪缆,但是這個還的人工切換,是不是覺得太麻煩了伞广,不要捉急拣帽,python還有一個比greenlet更強(qiáng)大的并且能夠自動切換任務(wù)的模塊 gevent
其原理是當(dāng)一個greenlet遇到IO(指的是input output 輸入輸出,比如網(wǎng)絡(luò)嚼锄、文件操作等)操作時减拭,比如訪問網(wǎng)絡(luò),就自動切換到其他的greenlet区丑,等到IO操作完成拧粪,再在適當(dāng)?shù)臅r候切換回來繼續(xù)執(zhí)行修陡。
由于IO操作非常耗時,經(jīng)常使程序處于等待狀態(tài)可霎,有了gevent為我們自動切換協(xié)程魄鸦,就保證總有g(shù)reenlet在運(yùn)行,而不是等待IO
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import gevent
def f():
for i in range(5):
print("%s:%d"%(gevent.getcurrent(),i))
g1 = gevent.spawn(f)
g2 = gevent.spawn(f)
g3 = gevent.spawn(f)
g1.join()
g2.join()
g3.join()
</pre>
運(yùn)行結(jié)果為:
<pre class="prettyprint hljs go" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><Greenlet at 0x1ba533f9598: f(5)>:0
<Greenlet at 0x1ba533f9598: f(5)>:1
<Greenlet at 0x1ba533f9598: f(5)>:2
<Greenlet at 0x1ba533f9598: f(5)>:3
<Greenlet at 0x1ba533f9598: f(5)>:4
<Greenlet at 0x1ba533f97b8: f(5)>:0
<Greenlet at 0x1ba533f97b8: f(5)>:1
<Greenlet at 0x1ba533f97b8: f(5)>:2
<Greenlet at 0x1ba533f97b8: f(5)>:3
<Greenlet at 0x1ba533f97b8: f(5)>:4
<Greenlet at 0x1ba533f99d8: f(5)>:0
<Greenlet at 0x1ba533f99d8: f(5)>:1
<Greenlet at 0x1ba533f99d8: f(5)>:2
<Greenlet at 0x1ba533f99d8: f(5)>:3
<Greenlet at 0x1ba533f99d8: f(5)>:4
</pre>
可以看到癣朗,3個greenlet是依次運(yùn)行而不是交替運(yùn)行
gevent的切換執(zhí)行
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import gevent
def f():
for i in range(5):
print("%s:%d"%(gevent.getcurrent(),i))
gevent.sleep(0)
g1=gevent.spawn(f)
g2=gevent.spawn(f)
g3=gevent.spawn(f)
g1.join()
g2.join()
g3.join()
</pre>
執(zhí)行結(jié)果為:
<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><Greenlet at 0x20a5e719598: f>:0
<Greenlet at 0x20a5e7197b8: f>:0
<Greenlet at 0x20a5e7199d8: f>:0
<Greenlet at 0x20a5e719598: f>:1
<Greenlet at 0x20a5e7197b8: f>:1
<Greenlet at 0x20a5e7199d8: f>:1
<Greenlet at 0x20a5e719598: f>:2
<Greenlet at 0x20a5e7197b8: f>:2
<Greenlet at 0x20a5e7199d8: f>:2
<Greenlet at 0x20a5e719598: f>:3
<Greenlet at 0x20a5e7197b8: f>:3
<Greenlet at 0x20a5e7199d8: f>:3
<Greenlet at 0x20a5e719598: f>:4
<Greenlet at 0x20a5e7197b8: f>:4
<Greenlet at 0x20a5e7199d8: f>:4
</pre>
3個greenlet交替運(yùn)行
gevent.spawn 啟動協(xié)程拾因,參數(shù)為函數(shù)名稱,參數(shù)名稱
3旷余、gevent并發(fā)下載器
monkey可以使一些阻塞的模塊變得不阻塞绢记,機(jī)制:遇到IO操作則自動切換,手動切換可以用gevent.sleep(0)
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">from gevent import monkey
import gevent
import urllib.request
有I/O時需要這一句,如果沒有這句話就會有阻塞狀態(tài)荣暮,加上就沒有阻塞
monkey.patch_all()
def myDownLoad(url):
print("GET:%s"%url)
resp = urllib.request.urlopen(url)
data = resp.read()
print("%d bytes received from %s"%(len(data),url))
gevent.joinall((
gevent.spawn(myDownLoad,"http://www.baidu.com/"),
gevent.spawn(myDownLoad,"https://apple.com"),
gevent.spawn(myDownLoad,"https://www.cnblogs.com/Se7eN-HOU/")
))
</pre>
運(yùn)行結(jié)果為:
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">GET:http://www.baidu.com/
GET:https://apple.com
GET:https://www.cnblogs.com/Se7eN-HOU/
153390 bytes received from http://www.baidu.com/
18880 bytes received from https://www.cnblogs.com/Se7eN-HOU/
58865 bytes received from https://apple.com
</pre>
從上能夠看到是先發(fā)送的獲取baidu的相關(guān)信息庭惜,然后依次是apple,cnblogs但是收到數(shù)據(jù)的先后順序不一定與發(fā)送順序相同罩驻,這也就體現(xiàn)出了異步穗酥,即不確定什么時候會收到數(shù)據(jù),順序不一定.
上面如果沒有下面這句代碼惠遏,
<pre class="prettyprint hljs makefile" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#有I/O時需要這一句,如果沒有這句話就會有阻塞狀態(tài)砾跃,加上就沒有阻塞
monkey.patch_all()
</pre>
執(zhí)行結(jié)果如下
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">GET:http://www.baidu.com/
153378 bytes received from http://www.baidu.com/
GET:https://apple.com
58865 bytes received from https://apple.com
GET:https://www.cnblogs.com/Se7eN-HOU/
18880 bytes received from https://www.cnblogs.com/Se7eN-HOU/
</pre>
每請求一個網(wǎng)站就會等著請求完畢了在執(zhí)行第二個,在請求的過程中节吮,網(wǎng)速慢等待的狀態(tài)就是在阻塞抽高。
五、asyncio
我們都知道透绩,現(xiàn)在的服務(wù)器開發(fā)對于IO調(diào)度的優(yōu)先級控制權(quán)已經(jīng)不再依靠系統(tǒng)翘骂,都希望采用協(xié)程的方式實(shí)現(xiàn)高效的并發(fā)任務(wù),如js帚豪、lua等在異步協(xié)程方面都做的很強(qiáng)大碳竟。
Python 在3.4版本也加入了協(xié)程的概念,并在3.5確定了基本完善的語法和實(shí)現(xiàn)方式狸臣。同時3.6也對其進(jìn)行了如解除了await和yield在同一個函數(shù)體限制等相關(guān)的優(yōu)化莹桅。
event_loop 事件循環(huán):程序開啟一個無限的循環(huán),程序員會把一些函數(shù)注冊到事件循環(huán)上烛亦。當(dāng)滿足事件發(fā)生的時候诈泼,調(diào)用相應(yīng)的協(xié)程函數(shù)。
coroutine 協(xié)程:協(xié)程對象煤禽,指一個使用async關(guān)鍵字定義的函數(shù)铐达,它的調(diào)用不會立即執(zhí)行函數(shù),而是會返回一個協(xié)程對象檬果。協(xié)程對象需要注冊到事件循環(huán)瓮孙,由事件循環(huán)調(diào)用贾节。
task 任務(wù):一個協(xié)程對象就是一個原生可以掛起的函數(shù),任務(wù)則是對協(xié)程進(jìn)一步封裝衷畦,其中包含任務(wù)的各種狀態(tài)栗涂。
future: 代表將來執(zhí)行或沒有執(zhí)行的任務(wù)的結(jié)果。它和task上沒有本質(zhì)的區(qū)別
async/await 關(guān)鍵字:python3.5 用于定義協(xié)程的關(guān)鍵字祈争,async定義一個協(xié)程斤程,await用于掛起阻塞的異步調(diào)用接口。
1菩混、創(chuàng)建協(xié)程
首先定義一個協(xié)程忿墅,在def前加入async聲明,就可以定義一個協(xié)程函數(shù)沮峡。
一個協(xié)程函數(shù)不能直接調(diào)用運(yùn)行疚脐,只能把協(xié)程加入到事件循環(huán)loop中。asyncio.get_event_loop方法可以創(chuàng)建一個事件循環(huán)邢疙,然后使用run_until_complete將協(xié)程注冊到事件循環(huán)棍弄,并啟動事件循環(huán)。
例如:
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import asyncio
async def fun():
print("---協(xié)程中---")
def main():
print("---主線程中---")
loop = asyncio.get_event_loop()
loop.run_until_complete(fun())
if name == "main":
main()
</pre>
運(yùn)行結(jié)果:
<pre class="hljs lua" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">---主線程中---
---協(xié)程中---
</pre>
二疟游、任務(wù)對象task
協(xié)程對象不能直接運(yùn)行呼畸,在注冊事件循環(huán)的時候,其實(shí)是run_until_complete方法將協(xié)程包裝成為了一個任務(wù)(task)對象颁虐。所謂task對象是Future類的子類蛮原。保存了協(xié)程運(yùn)行后的狀態(tài),用于未來獲取協(xié)程的結(jié)果另绩。
例如:
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import asyncio
async def fun():
print("---協(xié)程中---")
return "Se7eN_HOU"
def main():
print("---主線程中---")
loop = asyncio.get_event_loop()
#創(chuàng)建task
task = loop.create_task(fun())
print(task)
loop.run_until_complete(task)
print(task)
if name == "main":
main()
</pre>
運(yùn)行結(jié)果為:
<pre class="prettyprint hljs kotlin" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">---主線程中---
<Task pending coro=<fun() running at C:/Users/Se7eN_HOU/PycharmProjects/PythonLesson/test.py:4>>
---協(xié)程中---
<Task finished coro=<fun() done, defined at C:/Users/Se7eN_HOU/PycharmProjects/PythonLesson/test.py:4> result='Se7eN_HOU'>
</pre>
創(chuàng)建task后儒陨,task在加入事件循環(huán)之前是pending狀態(tài),因為fun()中沒有耗時的阻塞操作笋籽,task很快就執(zhí)行完畢了蹦漠。后面打印的finished狀態(tài)。
asyncio.ensure_future 和 loop.create_task都可以創(chuàng)建一個task干签,run_until_complete的參數(shù)是一個futrue對象津辩。
三、綁定回調(diào)
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import asyncio
協(xié)程
async def fun():
print("---協(xié)程中---")
return "Se7eN_HOU"
協(xié)程的回調(diào)函數(shù)
def callback(future):
#future.result是協(xié)程的返回值
print("callBack:%s"%future.result())
def main():
print("---主線程中---")
#創(chuàng)建loop回路
loop = asyncio.get_event_loop()
#創(chuàng)建task
task = loop.create_task(fun())
#調(diào)用回調(diào)函數(shù)
task.add_done_callback(callback)
print(task)
loop.run_until_complete(task)
print(task)
if name == "main":
main()
</pre>
運(yùn)行結(jié)果為:
<pre class="prettyprint hljs groovy" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">---主線程中---
<Task pending coro=<fun() running at C:/Users/Se7eN_HOU/PycharmProjects/PythonLesson/test.py:4> cb=[callback() at C:/Users/Se7eN_HOU/PycharmProjects/PythonLesson/test.py:9]>
---協(xié)程中---
callBack:Se7eN_HOU
<Task finished coro=<fun() done, defined at C:/Users/Se7eN_HOU/PycharmProjects/PythonLesson/test.py:4> result='Se7eN_HOU'>
</pre>
也可以使用ensure_future獲取返回值
例如:
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import asyncio
協(xié)程
async def fun():
print("---協(xié)程中---")
return "Se7eN_HOU"
協(xié)程的回調(diào)函數(shù)
def callback(future):
#future.result是協(xié)程的返回值
#print("callBack:%s"%future.result())
def main():
#創(chuàng)建loop回路
loop = asyncio.get_event_loop()
#創(chuàng)建task
#task = loop.create_task(fun())
#調(diào)用回調(diào)函數(shù)
#task.add_done_callback(callback)
task = asyncio.ensure_future(fun())
loop.run_until_complete(task)
print("fun函數(shù)的返回值是:%s"%format(task.result()))
if name == "main":
main()
</pre>
運(yùn)行結(jié)果為:
<pre class="hljs kotlin" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">---協(xié)程中---
fun函數(shù)的返回值是:Se7eN_HOU
</pre>
四容劳、await阻塞喘沿,執(zhí)行并發(fā)
使用async可以定義協(xié)程對象,使用await可以針對耗時的操作進(jìn)行掛起竭贩,就像生成器里的yield一樣蚜印,函數(shù)讓出控制權(quán)。協(xié)程遇到await留量,事件循環(huán)將會掛起該協(xié)程窄赋,執(zhí)行別的協(xié)程哟冬,直到其他的協(xié)程也掛起或者執(zhí)行完畢,再進(jìn)行下一個協(xié)程的執(zhí)行忆绰。
耗時的操作一般是一些IO操作浩峡,例如網(wǎng)絡(luò)請求,文件讀取等错敢。我們使用asyncio.sleep函數(shù)來模擬IO操作翰灾。協(xié)程的目的也是讓這些IO操作異步化。
例如:
<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import asyncio
async def test1():
print("---1---")
await asyncio.sleep(5)
print("---2---")
async def test2():
print("---3---")
await asyncio.sleep(1)
print("---4---")
async def test3():
print("---5---")
await asyncio.sleep(3)
print("---6---")
def main():
loop = asyncio.get_event_loop()
print("begin")
t1 = test1()
t2 = test2()
t3 = test3()
tasks1 = [t1,t2,t3]
loop.run_until_complete(asyncio.wait(tasks1))
print("end")
loop.close()
if name=="main":
main()
</pre>
運(yùn)行結(jié)果為:
<pre class="hljs sql" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">begin
---3---
---1---
---5---
---4---
---6---
---2---
end</pre>