Python并發之異步I/O(async,await)
背景
Python有很長一段的異步編程歷史,特別是twisted
,gevent
和一些無堆棧的Python項目。
異步編程因為一些好的原因在這些年來越來越受關注。盡管相對于傳統的線性風格更難一點,但是卻是值得的:因為異步編程有更高的效率。
舉個例子:在Http請求方面,Python異步協程可以提交請求然后去做隊列中其他等待的任務,讓它慢慢請求,而不是傳統的一直等它請求到完成為止,這樣的話會浪費更多的時間與資源。總之異步編程能讓你的代碼在處于等待資源狀態時處理其他任務。
在Python3.4中,asyncio
產生了。而在Python3.5中,有加入了對async def
和await
新的語法支持,讓我們看一看它們怎么工作的。
協程
在Python中,一個異步的函數我們通常叫它協程。之前我們在講解yield
的時候也已經講過yield
語法在協程中的基本使用了,這次同樣是協程,但卻是不同的語法。
#在Python 3.4中, 創建一個協程我們用asyncio.coroutine裝飾器:
async def double(x):
return x * 2
# 這是個協程對象
>>> double(6)
>>><coroutine object double at 0x115b59d58>
# 既然是協程,我們像之前yield協程那樣,預激活一下(注意這里用next(double(6)預激活會報錯)
>>> double(6).send(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: 12
# 好像差不多。
或者是這樣的:
async def ping_server(ip):
# ping code here...
# 用await語法代替"yield from"
async def ping_local():
return await ping_server('192.168.1.1')
建議使用Python3.5最新主流語法:async def
,await
asyncio的幾個重要結構
我這里都用英文展示,原生,易理解。怎么翻譯都繞口。
event loop:
An event loop essentially manages and distributes the execution of different tasks. It registers them and handles distributing the flow of control between them.
Coroutines:
Coroutines are special functions that work similarly Python generators that on await they release the flow of control back to the event loop. A coroutine needs to be scheduled to run using the event loop, to do this we create a Task, which is a type of Future.
Futures:
Futures are objects that represent the result of a task that may or may not have been executed. This result may be an exception.
理解基本架構很重要,理解協程,future,task的概念重中之重,至于什么內部實現,真的挺復雜的,無需關心。
在這邊的時候我卡了很長時間:future
與Task
的區別是什么????
future在多線程說過,future表示終將發生的事情,而確定某件事會發生的唯一方式是執行的時間已經排定。(你不排定它,它就是個協程),那怎么排定?
BaseEventLoop.create_task(...)
或者 asyncio.ensure_future
方法接收一個協程,排定它的運行時間,然后返回一個asyncio.Task
實例——也是 asyncio.Future 類的實例,因為 Task 是Future 的子類,用于包裝協程。這與調用 Executor.submit(...) 方法創建Future
實例是一個道理
這句話我讀了好多遍,意思是不是說future跟task是同一樣東西。對于event loop來說,一個包裝了協程的future,就是循環中的一個task?我是這么理解的。
我們無法確定future啥時完成結束,但是總歸結束(無論報錯還是返回值)的,因為我們已經給它排定了時間。
例子1
少廢話,直接看例子。
import asyncio
import time
start = time.time()
def tic():
return 'at %1.1f seconds' % (time.time() - start)
async def gr1():
# Busy waits for a second, but we don't want to stick around...
print('gr1 started work: {}'.format(tic()))
# 暫停兩秒,但不阻塞時間循環,下同
await asyncio.sleep(2)
print('gr1 ended work: {}'.format(tic()))
async def gr2():
# Busy waits for a second, but we don't want to stick around...
print('gr2 started work: {}'.format(tic()))
await asyncio.sleep(2)
print('gr2 Ended work: {}'.format(tic()))
async def gr3():
print("Let's do some stuff while the coroutines are blocked, {}".format(tic()))
await asyncio.sleep(1)
print("Done!")
# 事件循環
ioloop = asyncio.get_event_loop()
# tasks中也可以使用asyncio.ensure_future(gr1())..
tasks = [
ioloop.create_task(gr1()),
ioloop.create_task(gr2()),
ioloop.create_task(gr3())
]
ioloop.run_until_complete(asyncio.wait(tasks))
ioloop.close()
output:
gr1 started work: at 0.0 seconds
gr2 started work: at 0.0 seconds
Let's do some stuff while the coroutines are blocked, at 0.0 seconds
Done!
gr2 Ended work: at 2.0 seconds
gr1 ended work: at 2.0 seconds
asyncio.wait(...)
協程的參數是一個由future或協程構成的可迭代對象;wait 會分別
把各個協程包裝進一個 Task 對象。最終的結果是,wait 處理的所有對象都通過某種方式變成 Future 類的實例。wait 是協程函數,因此返回的是一個協程或生成器對象。ioloop.run_until_complete
方法的參數是一個future或協程。如果是協程,run_until_complete
方法與 wait 函數一樣,把協程包裝進一個 Task 對象中。在 asyncio 包中,future和協程關系緊密,因為可以使用 yield from 從asyncio.Future 對象中產出結果。這意味著,如果 foo 是協程函數(調用后返回協程對象),抑或是返回Future 或 Task 實例的普通函數,那么可以這樣寫:res = yield from foo()。這是 asyncio 包的 API 中很多地方可以互換協程與期物的原因之一。 例如上面的例子中tasks可以改寫成協程列表:
tasks = [gr1(), gr(2), gr(3)]
詳細的各個類說明,類方法,傳參,以及方法返回的是什么類型都可以在官方文檔上仔細研讀,多讀幾遍,方有體會。
好好體會event loop中運作圖.....如下所示:
例子2
例子1只是用來講述語法,實際用途中還是得看這個例子。上節多線程與多進程的例子,獲取有效網址。
import asyncio
import time
import aiohttp
import async_timeout
msg = "http://www.nationalgeographic.com.cn/photography/photo_of_the_day/{}.html"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'
}
urls = [msg.format(i) for i in range(4500, 5057)]
async def fetch(session, url):
with async_timeout.timeout(10):
async with session.get(url) as response:
return response.status
async def main(url):
async with aiohttp.ClientSession() as session:
status = await fetch(session, url)
return status
if __name__ == '__main__':
start = time.time()
loop = asyncio.get_event_loop()
tasks = [main(url) for url in urls]
# 返回一個列表,內容為各個tasks的返回值
status_list = loop.run_until_complete(asyncio.gather(*tasks))
print(len([status for status in status_list if status==200]))
end = time.time()
print("cost time:", end - start)
任重而道遠
- 在封閉的代碼塊中使用一些新的關鍵字就能實現異步功能
- 我對于這一塊還是處于小白狀態,掌握不是很全面
- 多Google,多Google,多Google.....
參考資料
http://stackabuse.com/python-async-await-tutorial/
https://hackernoon.com/asyncio-for-the-working-python-developer-5c468e6e2e8e
https://docs.python.org/3/library/asyncio-task.html
http://quietlyamused.org/blog/2015/10/02/async-python/