您好,登錄后才能下訂單哦!
不懂Python中的asyncio庫-asyncio的概念是什么?其實想解決這個問題也不難,下面讓小編帶著大家一起學習怎么去解決,希望大家閱讀完這篇文章后大所收獲。
核心概念
asyncio里面主要有4個需要關注的基本概念
Eventloop
Eventloop可以說是asyncio應用的核心,是中央總控。Eventloop實例提供了注冊、取消和執行任務和回調的方法。
把一些異步函數(就是任務,Task,一會就會說到)注冊到這個事件循環上,事件循環會循環執行這些函數(但同時只能執行一個),當執行到某個函數時,如果它正在等待I/O返回,事件循環會暫停它的執行去執行其他的函數;當某個函數完成I/O后會恢復,下次循環到它的時候繼續執行。因此,這些異步函數可以協同(Cooperative)運行:這就是事件循環的目標。
Coroutine
協程(Coroutine)本質上是一個函數,特點是在代碼塊中可以將執行權交給其他協程:
? cat coro1.py import asyncio async def a(): print('Suspending a') await asyncio.sleep(0) print('Resuming a') async def b(): print('In b') async def main(): await asyncio.gather(a(), b()) if __name__ == '__main__': asyncio.run(main())
這里面有4個重要關鍵點:
協程要用async def聲明,Python 3.5時的裝飾器寫法已經過時,我就不列出來了。
asyncio.gather用來并發運行任務,在這里表示協同的執行a和b2個協程
在協程a中,有一句await asyncio.sleep(0),await表示調用協程,sleep 0并不會真的sleep(因為時間為0),但是卻可以把控制權交出去了。
asyncio.run是Python 3.7新加的接口,要不然你得這么寫:
loop = asyncio.get_event_loop() loop.run_until_complete(main()) loop.close()
好了,我們先運行一下看看:
? python coro1.py Suspending a In b Resuming a
看到了吧,在并發執行中,協程a被掛起又恢復過。
Future
接著說Future,它代表了一個「未來」對象,異步操作結束后會把最終結果設置到這個Future對象上。Future是對協程的封裝,不過日常開發基本是不需要直接用這個底層Future類的。我在這里只是演示一下:
In : def c(): ...: print('Inner C') ...: return 12 ...: In : future = loop.run_in_executor(None, c) # 這里沒用await,None 表示默認的 executor Inner C In : future # 雖然c已經執行了,但是狀態還是 pending。 Out: <Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/local/lib/python3.7/asyncio/futures.py:348]> In : future.done() # 還沒有完成 Out: False In : for a in dir(future): ...: if not a.startswith('_'): ...: print(a) ...: add_done_callback cancel cancelled done exception get_loop remove_done_callback result set_exception set_result
可以對這個Future實例添加完成后的回調(add_done_callback)、取消任務(cancel)、設置最終結果(set_result)、設置異常(如果有的話,set_exception)等。現在我們讓Future完成:
In : await future Out: 12 In : future Out: <Future finished result=12> In : future.done() Out: True In : future.result() Out: 12
看到了吧,await之后狀態成了finished。這里順便說一下,一個對象怎么樣就可以被await(或者說怎么樣就成了一個awaitable對象)呢?給類實現一個__await__方法,Python版本的Future的實現大概如下:
def __await_(self): if not self.done(): self._asyncio_future_blocking = True yield self if not self.done(): raise RuntimeError("await wasn't used with future") return self.result()
這樣就可以await future了,那為什么await future后Future的狀態就能改變呢,這是因為用loop.run_in_executor創建的Future注冊了一個回調(通過asyncio.futures.wrap_future,加了一個_call_set_state回調, 有興趣的可以通過延伸閱讀鏈接2找上下文)。
__await__里面的yield self不要奇怪,主要是為了兼容__iter__,給舊的yield from用:
In : future = loop.run_in_executor(None, c) Inner C In : future Out: <Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/local/lib/python3.7/asyncio/futures.py:348]> In : def spam(): ...: yield from future ...: In : s = spam() In : next(s) Out: <Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/local/lib/python3.7/asyncio/futures.py:348]> 新的替代yield from的用法await必須在異步函數(用 async def申明)中使用: In : def spam(): ...: await future ...: File "cell_name", line 5 SyntaxError: 'await' outside async function
Task
Eventloop除了支持協程,還支持注冊Future和Task2種類型的對象,那為什么要存在Future和Task這2種類型呢?
先回憶前面的例子,Future是協程的封裝,Future對象提供了很多任務方法(如完成后的回調、取消、設置任務結果等等),但是開發者并不需要直接操作Future這種底層對象,而是用Future的子類Task協同的調度協程以實現并發。
Task非常容易創建和使用:
# 或者用task = loop.create_task(a()) In : task = asyncio.ensure_future(a()) In : task Out: <Task pending coro=<a() running at /Users/dongwm/mp/2019-05-22/coro1.py:4>> In : task.done() Out: False In : await task Suspending a Resuming a In : task Out: <Task finished coro=<a() done, defined at /Users/dongwm/mp/2019-05-22/coro1.py:4> result=None> In : task.done() Out: True
asyncio并發的正確/錯誤姿勢
在代碼中使用async/await是不是就能發揮asyncio的并發優勢么,其實是不對的,我們先看個例子:
async def a(): print('Suspending a') await asyncio.sleep(3) print('Resuming a') async def b(): print('Suspending b') await asyncio.sleep(1) print('Resuming b') async def s1(): await a() await b()
有2個協程a和b,分別sleep1秒和3秒,如果協程可以并發執行,那么執行時間應該是sleep最大的那個值(3秒),現在它們都在s1協程里面被調用。大家先猜一下s1會運行幾秒?
我們寫個小程序驗證一下:
def show_perf(func): print('*' * 20) start = time.perf_counter() asyncio.run(func()) print(f'{func.__name__} Cost: {time.perf_counter() - start}')
大家注意我這個時間計數用的方法,沒有用time.time,而是用了Python 3.3新增的time.perf_counter它是現在推薦的用法。我們在IPython里面驗證下:
In : from coro2 import * In : show_perf(s1) ******************** Suspending a Resuming a Suspending b Resuming b s1 Cost: 4.009796932999961
看到了吧,4秒!!!,相當于串行的執行了(sleep 3 + 1)。這是錯誤的用法,應該怎么用呢,前面的asyncio.gather就可以:
async def c1(): await asyncio.gather(a(), b()) In : show_perf(c1) ******************** Suspending a Suspending b Resuming b Resuming a c1 Cost: 3.002452698999832
看到了吧,3秒!另外一個是asyncio.wait:
async def c2(): await asyncio.wait([a(), b()]) In : show_perf(c2) ... c2 Cost: 3.0066957049998564
同樣是3秒。先別著急,gather和wait下篇文章還會繼續對比。還有一個方案就是用asyncio.create_task:
async def c3(): task1 = asyncio.create_task(a()) task2 = asyncio.create_task(b()) await task1 await task2 async def c4(): task = asyncio.create_task(b()) await a() await task In : show_perf(c3) ... c3 Cost: 3.002332438999929 In : show_perf(c4) ... c4 Cost: 3.002270970000154
都是3秒。asyncio.create_task相當于把協程封裝成Task。不過大家要注意一個錯誤的用法:
async def s2(): await asyncio.create_task(a()) await asyncio.create_task(b()) In : show_perf(s2) ... s2 Cost: 4.004671427999938
直接await task不會對并發有幫助*。asyncio.create_task是Python 3.7新增的高階API,是推薦的用法,其實你還可以用
asyncio.ensure_future和loop.create_task: async def c5(): task = asyncio.ensure_future(b()) await a() await task async def c6(): loop = asyncio.get_event_loop() task = loop.create_task(b()) await a() await task In : show_perf(c5) ... c5 Cost: 3.0033873750003295 In : show_perf(c6) ... c6 Cost: 3.006120122000084
感謝你能夠認真閱讀完這篇文章,希望小編分享Python中的asyncio庫-asyncio的概念是什么內容對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,遇到問題就找億速云,詳細的解決方法等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。