python3 携程_多任务(3):协程
代碼環境:python3.6
上一篇文章我們講了 python 中多線程的使用:點擊閱讀,現在我們講講 python 中的協程。
異步IO
我們知道,CPU 速度遠遠快于磁盤、網絡等 IO。在 IO 編程中,假如一個 IO 操作阻塞了當前線程,會導致其他代碼無法執行,所以我們使用多線程或者多進程來并發執行代碼。
但是,系統資源是有限的,一旦線程數量過多,CPU 的時間就花在線程切換上了,真正執行代碼的時間下降,導致性能嚴重下降。
針對這個問題,我們需要另一種解決方法:異步 IO。
異步 IO,即當代碼需要執行一個耗時的 IO 操作時,它只發出 IO 指令,并不等待 IO 結果,然后就去執行其他代碼了。一段時間后,當 IO 返回結果時,再通知 CPU 進行處理。
python中最初的協程
了解最初的協程有助于我們理解后面現代協程的用法。
協程這個概念并不是 python 首次提出的,而是從其他語言借鑒過來的。
我們知道,兩個普通函數的調用是按順序的,比如A函數調用B函數,B執行完畢返回結果給A,A執行完畢。
協程看上去也是函數,如果協程A調用協程B,在執行過程中,協程B可以中斷,轉而執行A,再在適當的時候返回B接著從中斷處往下執行。
協程的這種執行特點,恰好符合我們的需求:通過協程實現異步 IO 編程。
生成器進化成協程
python 基于 generator 進行一系列功能改進后得到協程,語法上都是定義體中包含 yield 關鍵字。
在協程中,yield 不僅可以返回值,還能接收調用者通過.send()方法發出的參數。yield 通常出現在表達式右邊,如:data = yield something。如果 yield 后面沒有表達式,說明此時 yield 只負責接收數據,協程始終返回None。
簡單協程的基本行為
舉個簡單例子:
In [1]: def my_coroutine():
...: print('協程被激活')
...: while True:
# yield 后面不跟表達式,這里只接收 send() 傳過來的數據
...: x = yield
...: print(f'協程接收到參數:{x}')
...:
In [2]: my_corou = my_coroutine()
# 可查看協程當前狀態
In [3]: from inspect import getgeneratorstate
In [4]: getgeneratorstate(my_corou)
Out[4]: 'GEN_CREATED'
# 激活協程,此處可用 my_corou.send(None) 代替
In [5]: next(my_corou)
協程被激活
In [6]: getgeneratorstate(my_corou)
Out[6]: 'GEN_SUSPENDED'
In [7]: return_value = my_corou.send(99)
協程接收到參數:99
In [8]: print(return_value)
None
In [9]: my_corou.close()
In [10]: getgeneratorstate(my_corou)
Out[10]: 'GEN_CLOSED'
通過例子我們主要了解的是,協程需要手動激活才能真正調用,協程在不需要的時候要記得關閉。
用協程改進生產者-消費者模型
傳統生產者-消費者模型中,一個線程寫消息,另一個線程取消息,通過鎖機制控制隊列和等待,但是一不小心可能死鎖。
如果改用協程,生產者生產消息后,直接通過 yield 跳轉到消費者開始執行,待消費者執行完畢后,再切換回生產者繼續生產,整個流程無鎖且效率高:
from inspect import getgeneratorstate
def consumer():
r = '200 OK'
while True:
# yield接收生產者的數據賦值給n,并把處理結果狀態r返回
n = yield r
print(f'[CONSUMER] 消費了:{n}')
def producer(c):
# 別忘了激活協程
c.send(None)
n = 0
while n < 5:
n = n + 1
print(f'[PRODUCER] 生產了:{n}')
# 一旦生產了東西,通過c.send()切換到consumer執行
# consumer處理數據后通過yield返回結果狀態,這里獲取返回內容
r = c.send(n)
print(f'[PRODUCER] 消費者返回的處理結果:{r}')
print(f'生產者不生產了,看看當前consumer狀態:{getgeneratorstate(c)}')
c.close()
print(f'關閉consumer,看看當前consumer狀態:{getgeneratorstate(c)}')
if __name__ == "__main__":
producer(consumer())
上面例子整個流程只由一個線程執行且無鎖,生產者和消費者協作完成任務,這種屬于協作式多任務,跟多線程這種搶占式多任務要區分開。
asyncio
在 python3.4 版本中,開始引入標準庫asyncio直接內置了對異步 IO 的支持。
asyncio的編程模型就是一個消息循環。我們從asyncio模塊中直接獲取一個EventLoop的引用,然后把需要執行的協程扔到EventLoop中執行,就實現了異步 IO。
先簡單介紹下asyncio涉及到的一些詞語:
Future:一個對象,表示異步執行的操作。通常情況下自己不應該創建Future,而只能由并發框架如asyncio實例化。
Task:在EventLoop中負責執行協程的任務,是Future的子類。換句話說,Task就是Future,但反過來不一定。
下面是asyncio常用API:
asyncio.get_event_loop():獲取一個EventLoop對象,用來運行協程
asyncio.iscoroutine(obj):判斷一個對象是否是協程。
asyncio.sleep(delay):直接當做是一個耗時多少秒的協程即可。
asyncio.ensure_future(coro_or_future):入參是協程,則激活協程,返回一個Task對象;如果入參是Future,則將入參直接返回。
asyncio.gather(coros_or_futures):按入參中協程的順序保存協程的執行結果,大部分情況下使用。
asyncio.wait(futures):對比gather,不一定按入參順序返回執行結果。返回包含已完成和掛起的Task,可通過接收參數return_when選擇返回結果的時機,按實際情況使用。
我們將在下面結合新的關鍵字async/await來舉例說明。
async/await
為了簡化使用和標識異步 IO,從 python3.5 版本開始引入新的語法糖async/await,用async把一個generator標記為協程函數,然后在協程內部用await調用另一個協程實現異步操作。
注意:
用async標記協程函數,調用該函數時協程尚未激活,激活該函數可以用await或者yield from,也可以通過ensure_future()或者AbstractEventLoop.create_task()調度執行。
舉個例子:
from asyncio import sleep as aiosleep, gather, get_event_loop
async def compute(x, y):
print("計算 %s + %s ..." % (x, y))
await aiosleep(1)
return x + y
async def print_sum(x, y):
result = await compute(x, y)
print("%s + %s = %s" % (x, y, result))
async def coro_main():
'''一般我們會寫一個 coroutine 的 main 函數,專門負責管理協程'''
await gather(print_sum(1, 2), print_sum(4, 9))
def main():
aioloop = get_event_loop()
# 內部使用ensure_future()激活協程
aioloop.run_until_complete(coro_main())
aioloop.close()
if __name__ == "__main__":
main()
執行結果:
計算 1 + 2 ...
計算 4 + 9 ...
(暫停約1秒,實際輸出沒有這行)
1 + 2 = 3
4 + 9 = 13
觀察例子運行結果,我們看到:
當協程開始計算1+2前還有一個耗時 1 秒的 IO 操作,當前線程并未等待,而是去執行其他協程計算4+9,實現了并發執行。
協程結果按gather入參的順序打印。
總結
面對 CPU 高速執行和 IO 設備的龜速嚴重不匹配問題,我們至少要知道兩種解決方法:使用多進程和多線程并發執行代碼;使用異步 IO 執行代碼。
python 協程是基于生成器改進后得到的,底部實現都是定義體中包含yield關鍵字。
協程屬于協作式多任務,整個流程無需鎖,跟多線程這種搶占式多任務要區分開。
asyncio支持異步 IO,我們從asyncio模塊中直接獲取一個EventLoop的引用,然后把需要執行的協程扔到EventLoop中執行,就實現了異步 IO。
定義協程函數時,我們用async標記協程函數,然后在協程內部用await調用另一個協程實現異步操作。
總結
以上是生活随笔為你收集整理的python3 携程_多任务(3):协程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android客户端访问服务端,服务端返
- 下一篇: android设置提交的隐藏域以及在on