前言
多進(jìn)程和多線程在實(shí)際編程中用的已經(jīng)非常多了,這篇文章的作用是記錄下學(xué)習(xí)協(xié)程的心得體會,爭取一篇文章搞定.
協(xié)程的好處不多說了,可以說是I/O密集型的利器.其實(shí)對于IO密集型任務(wù)我們還有一種選擇就是協(xié)程。協(xié)程,又稱微線程,英文名Coroutine,是運(yùn)行在單線程中的“并發(fā)”,協(xié)程相比多線程的一大優(yōu)勢就是省去了多線程之間的切換開銷,獲得了更高的運(yùn)行效率。Python中的異步IO模塊asyncio就是基本的協(xié)程模塊。
協(xié)程的切換不同于線程切換,是由程序自身控制的,沒有切換的開銷。協(xié)程不需要多線程的鎖機(jī)制,因?yàn)槎际窃谕粋€(gè)線程中運(yùn)行,所以沒有同時(shí)訪問數(shù)據(jù)的問題,執(zhí)行效率比多線程高很多。
1.python 生成器
1.1 python 生成器概述
三個(gè)概念: 迭代/迭代器/可迭代對象
什么是生成器?
生成器也是一個(gè)可迭代對象
[num for num in range(10)]
(num for num in range(10) )
上面是一個(gè)列表,下面是一個(gè)生成器
生成器的特點(diǎn)
- 迭代完成一次,就指向最后一個(gè)了,再次迭代就迭代不出來了.和list不同.
- 占用內(nèi)存大小不同
import sys
sys.getsizeof(10)
sys.getsizeof("Hello World")
sys.getsizeof(l)
sys.getsizeof(gen)
gen = (num for num in range(10000))
l = [num for num in range(10000)]
sys.getsizeof(l)
sys.getsizeof(gen)
生成器幾乎所占的內(nèi)存不發(fā)生變化,而列表則隨著元素的增多而增大!
生成器和列表非常類似,擁有一樣的迭代方式,內(nèi)存模型上更節(jié)省內(nèi)存空間,生成器只能遍歷一次。可以實(shí)現(xiàn)延時(shí)計(jì)算,并且用生成器代碼更簡潔。
1.2 關(guān)鍵字yield/yield from
Php, js,python C++ 中都有這個(gè)關(guān)鍵字,是一個(gè)比較高級的語法現(xiàn)象。
yield 只能再函數(shù)里面使用
def func():
print("Hello World!");
type(func)輸出 <class ‘function’>
def func():
print("Hello World!");
yield()
調(diào)用func()
注意 type(func()) 他返回了一個(gè)生成器?。。?br>
普通函數(shù) 調(diào)用 返回None 是因?yàn)楹瘮?shù)本來就返回none 就是普通函數(shù)的執(zhí)行。
yield 就把一個(gè)函數(shù)變成了一個(gè)生成器。
def func():
for i in range(10):
print(i)
def func2():
for i in range(10):
yield i
for i in func2():
print(i)
同樣生成了0到9的10個(gè)數(shù)字。
1.3 next/send函數(shù)
多進(jìn)程和多線程體現(xiàn)的是操作系統(tǒng)的能力,而協(xié)程體現(xiàn)的是程序員的流程控制能力。看下面的例子,甲,乙兩個(gè)工人模擬兩個(gè)工作任務(wù)交替進(jìn)行,在單線程內(nèi)實(shí)現(xiàn)了類似多線程的功能。
import time
def task1():
while True:
yield "<甲>也累了,讓<乙>工作一會兒"
time.sleep(1)
print("<甲>工作了一段時(shí)間.....")
def task2(t):
next(t)
while True:
print("-----------------------------------")
print("<乙>工作了一段時(shí)間.....")
time.sleep(2)
print("<乙>累了,讓<甲>工作一會兒....")
ret = t.send(None)
print(ret)
t.close()
if __name__ == '__main__':
t = task1()
task2(t)
輸出:
<乙>工作了一段時(shí)間.....
<乙>累了,讓<甲>工作一會兒....
<甲>工作了一段時(shí)間.....
<甲>也累了,讓<乙>工作一會兒
-----------------------------------
<乙>工作了一段時(shí)間.....
<乙>累了,讓<甲>工作一會兒....
<甲>工作了一段時(shí)間.....
<甲>也累了,讓<乙>工作一會兒
-----------------------------------
<乙>工作了一段時(shí)間.....
<乙>累了,讓<甲>工作一會兒....
<甲>工作了一段時(shí)間.....
<甲>也累了,讓<乙>工作一會兒
-----------------------------------
<乙>工作了一段時(shí)間.....
問題想一想為啥先執(zhí)行乙的語句,再執(zhí)行甲。
t = task1() 只是返回了一個(gè)生成器。task2(t) 的運(yùn)行,next(t) 使得進(jìn)入task1中執(zhí)行,并且遇到y(tǒng)ield停下,此時(shí)next(t)的返回值就是:<甲>也累了,讓<乙>工作一會兒
然后進(jìn)入task2的 while True, 直到遇到t.send(None), 執(zhí)行權(quán)反轉(zhuǎn)到task1,執(zhí)行yield的下面一句。
這里的send可以不傳none,
最早的時(shí)候,Python提供了yield關(guān)鍵字,用于制造生成器。也就是說,包含有yield的函數(shù),都是一個(gè)生成器!
yield的語法規(guī)則是:在yield這里暫停函數(shù)的執(zhí)行,并返回yield后面表達(dá)式的值(默認(rèn)為None),直到被next()方法再次調(diào)用時(shí),從上次暫停的yield代碼處繼續(xù)往下執(zhí)行。
每個(gè)生成器都可以執(zhí)行send()方法,為生成器內(nèi)部的yield語句發(fā)送數(shù)據(jù)。此時(shí)yield語句不再只是yield xxxx的形式,還可以是var = yield xxxx的賦值形式。它同時(shí)具備兩個(gè)功能,一是暫停并返回函數(shù),二是接收外部send()方法發(fā)送過來的值,重新激活函數(shù),并將這個(gè)值賦值給var變量!
def simple_coroutine():
print('-> 啟動協(xié)程')
y = 10
x = yield y
print('-> 協(xié)程接收到了x的值:', x)
my_coro = simple_coroutine()
ret = next(my_coro)
print(ret)
my_coro.send(100)
-> 啟動協(xié)程
10
-> 協(xié)程接收到了x的值: 1000
Traceback (most recent call last):
File "c:\Users\jianming_ge\Desktop\碳匯算法第一版\carbon_code\單線程模擬多線程.py", line 54, in <module>
my_coro.send(1000)
StopIteration
協(xié)程可以處于下面四個(gè)狀態(tài)中的一個(gè)。當(dāng)前狀態(tài)可以導(dǎo)入inspect模塊,使用inspect.getgeneratorstate(…) 方法查看,該方法會返回下述字符串中的一個(gè)。
‘GEN_CREATED’ 等待開始執(zhí)行。
‘GEN_RUNNING’ 協(xié)程正在執(zhí)行。
‘GEN_SUSPENDED’ 在yield表達(dá)式處暫停。
‘GEN_CLOSED’ 執(zhí)行結(jié)束。
因?yàn)閟end()方法的參數(shù)會成為暫停的yield表達(dá)式的值,所以,僅當(dāng)協(xié)程處于暫停狀態(tài)時(shí)才能調(diào)用 send()方法,例如my_coro.send(10)。不過,如果協(xié)程還沒激活(狀態(tài)是’GEN_CREATED’),就立即把None之外的值發(fā)給它,會出現(xiàn)TypeError。因此,始終要先調(diào)用next(my_coro)激活協(xié)程(也可以調(diào)用my_coro.send(None)),這一過程被稱作預(yù)激活。
除了send()方法,其實(shí)還有throw()和close()方法:
generator.throw(exc_type[, exc_value[, traceback]])
使生成器在暫停的yield表達(dá)式處拋出指定的異常。如果生成器處理了拋出的異常,代碼會向前執(zhí)行到下一個(gè)yield表達(dá)式,而產(chǎn)出的值會成為調(diào)用generator.throw()方法得到的返回值。如果生成器沒有處理拋出的異常,異常會向上冒泡,傳到調(diào)用方的上下文中。
generator.close()
使生成器在暫停的yield表達(dá)式處拋出GeneratorExit異常。如果生成器沒有處理這個(gè)異常,或者拋出了StopIteration異常(通常是指運(yùn)行到結(jié)尾),調(diào)用方不會報(bào)錯(cuò)。如果收到GeneratorExit異常,生成器一定不能產(chǎn)出值,否則解釋器會拋出RuntimeError異常。生成器拋出的其他異常會向上冒泡,傳給調(diào)用方。
1.4 StopInteration異常
平時(shí)在進(jìn)行python編程的時(shí)候,一般不關(guān)注異常,但學(xué)習(xí)生成器的時(shí)候,需要利用這個(gè)stopinteration進(jìn)行編程
1.5 利用生成器實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模型
1.6 生成器和協(xié)程的關(guān)系
生成器通過yield可以主動讓出cpu
多個(gè)生成器可以共同使用cpu,共同協(xié)作,有序進(jìn)行
生成器可以進(jìn)一步封裝成協(xié)程
2.生成器協(xié)程調(diào)度器
3.python事件驅(qū)動編程
4.實(shí)現(xiàn)協(xié)程調(diào)度器
5.python 協(xié)程生態(tài)
@asyncio.coroutine與yield from
@asyncio.coroutine:asyncio模塊中的裝飾器,用于將一個(gè)生成器聲明為協(xié)程。
下面這段代碼,我們創(chuàng)造了一個(gè)協(xié)程display_date(num, loop),然后它使用關(guān)鍵字yield from來等待協(xié)程asyncio.sleep(2)()的返回結(jié)果。而在這等待的2s之間它會讓出CPU的執(zhí)行權(quán),直到asyncio.sleep(2)返回結(jié)果。asyncio.sleep(2)模擬的其實(shí)就是一個(gè)耗時(shí)2秒的IO讀寫操作。
import asyncio
import datetime
@asyncio.coroutine # 聲明一個(gè)協(xié)程
def display_date(num, loop):
end_time = loop.time() + 10.0
while True:
print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
if (loop.time() + 1.0) >= end_time:
break
yield from asyncio.sleep(2) # 阻塞直到協(xié)程sleep(2)返回結(jié)果
loop = asyncio.get_event_loop() # 獲取一個(gè)event_loop
tasks = [display_date(1, loop), display_date(2, loop)]
loop.run_until_complete(asyncio.gather(*tasks)) # "阻塞"直到所有的tasks完成
loop.close()
Python3.5中對協(xié)程提供了更直接的支持,引入了async/await關(guān)鍵字。上面的代碼可以這樣改寫:使用async代替@asyncio.coroutine,使用await代替yield from,代碼變得更加簡潔可讀。從Python設(shè)計(jì)的角度來說,async/await讓協(xié)程獨(dú)立于生成器而存在,不再使用yield語法。
import asyncio
import datetime
async def display_date(num, loop): # 注意這一行的寫法
end_time = loop.time() + 10.0
while True:
print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
if (loop.time() + 1.0) >= end_time:
break
await asyncio.sleep(2) # 阻塞直到協(xié)程sleep(2)返回結(jié)果
loop = asyncio.get_event_loop() # 獲取一個(gè)event_loop
tasks = [display_date(1, loop), display_date(2, loop)]
loop.run_until_complete(asyncio.gather(*tasks)) # "阻塞"直到所有的tasks完成
loop.close()
asyncio模塊
asyncio的使用可分三步走:
創(chuàng)建事件循環(huán)
指定循環(huán)模式并運(yùn)行
關(guān)閉循環(huán)
通常我們使用asyncio.get_event_loop()方法創(chuàng)建一個(gè)循環(huán)。
運(yùn)行循環(huán)有兩種方法:一是調(diào)用run_until_complete()方法,二是調(diào)用run_forever()方法。run_until_complete()內(nèi)置add_done_callback回調(diào)函數(shù),run_forever()則可以自定義add_done_callback(),具體差異請看下面兩個(gè)例子。
使用run_until_complete()方法:文章來源:http://www.zghlxwxcb.cn/news/detail-433386.html
import asyncio
async def func(future):
await asyncio.sleep(1)
future.set_result('Future is done!')
if __name__ == '__main__':
loop = asyncio.get_event_loop()
future = asyncio.Future()
asyncio.ensure_future(func(future))
print(loop.is_running()) # 查看當(dāng)前狀態(tài)時(shí)循環(huán)是否已經(jīng)啟動
loop.run_until_complete(future)
print(future.result())
loop.close()
使用run_forever()方法:文章來源地址http://www.zghlxwxcb.cn/news/detail-433386.html
import asyncio
async def func(future):
await asyncio.sleep(1)
future.set_result('Future is done!')
def call_result(future):
print(future.result())
loop.stop()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
future = asyncio.Future()
asyncio.ensure_future(func(future))
future.add_done_callback(call_result) # 注意這行
try:
loop.run_forever()
finally:
loop.close()
到了這里,關(guān)于[python] 協(xié)程學(xué)習(xí)從0到1,配合案例,徹底理解協(xié)程,耗費(fèi)資源不增加,效果接近多線程的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!