非同步編程 101:asyncio中的 for 循環
前面寫的非同步編程的幾篇文章:
- 非同步編程 101: 是什麼、小試Python asyncio
- 非同步編程 101:Python async await發展簡史
- 非同步編程 101:寫一個事件循環
廢話不多說,上代碼:
這個代碼只適用於Python3.7,因為
asyncio.run()
在 Python3.7才提出
import asyncio
async def heavy_task():
await asyncio.sleep(2)
async def main():
for _ in range(100):
await heavy_task()
if __name__ == __main__:
asyncio.run(main())
請問上面這個代碼需要多久完成?答案是20秒,而不是我們期望的那樣在2秒內完成。
理解原因需要先搞明白 await
到底幹了什麼:事件循環執行到 await
的時候,會把當前的協程掛起(暫停),然後看看當前的事件循環池裡面有哪些其他可以執行的協程,接著繼續執行其他的協程。
main()
中的 for 循環是一個整體, awaitheavy_task()
會把整個 main()
掛起,等到await的 heavy_task()
執行完了(也就是兩秒後),接著再返回 main()
,從上次落下的地方繼續。而上次落下的地方,還是在這個 for 循環裡面的。
所以,這個程序是非同步的嗎?可以肯定的說是的,針對整個程序是非同步的。但是對於 main()
,它的 for
循環還是阻塞的。
上述問題的根源在於:我們沒能及時往事件循環裡面添加協程。 for
循環要等到 heavy_task()
結束了再創建下一個協程。要解決這個可以用 asyncio.gather()
:
前面一篇文章裡面我們的那個代碼:先把所有的協程事先創建好,然後一次性交給 asyncio.gather()
。
import time
import asyncio
import aiohttp
async def fetch_async(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
status_code = resp.status
print(status_code)
async def visit_async():
start = time.time()
tasks = []
for _ in range(100):
tasks.append(fetch_async(URL))
await asyncio.gather(*tasks)
end = time.time()
print("visit_async tasks %.2f seconds" % (end - start))
if __name__ == __main__:
loop = asyncio.get_event_loop()
loop.run_until_complete(visit_async())
如果你像我一樣真正熱愛計算機科學,喜歡研究底層邏輯,歡迎關注我的微信公眾號: