由於 Python 中的協程是運行在一條線程中通過消息隊列調控的,如果運行的線程堵塞了那么就會造成消息隊列阻塞。為了避免這種情況的發生我們需要區分 IO 密集型任務和 CUP 密集型任務,在 IO 密集型任務中,協程發生阻塞后會在消息隊列中掛起轉而執行其它協程,而如果是 CUP 密集型任務則需要新開辟線程並放入執行以避免阻塞當前線程。
1 """ 2 協程中 IO 密集型任務和 CPU 密集型任務的區別: 3 4 協程碰到阻塞 IO 后會掛起繼續執行下一個任務,在 IO 結束后恢復運行。 5 而 CPU 密集型任務會阻塞整條消息隊列(因為協程都是在一條線程里運行的),后果就是異步變成了同步。 6 7 IO 密集型任務舉例:請求網絡(aiohttp)、讀寫文件(aiofiles)、操作數據庫等,需要用到第三方庫 8 CPU 密集型任務舉例:解析網頁、數值計算等。 9 10 碰到 CUP 密集型任務為了防止隊列阻塞,需要用到線程池,aio提供了相應的接口,可見官方文檔。 11 """ 12 import asyncio 13 import time 14 15 async def work(n): 16 print(f'{n} is working.') # 這里會同時打印三條信息 17 18 await asyncio.sleep(1) # 模擬 IO 19 20 print(f'{n} is awaked') # 這里只會打印單條信息,因為下方是 CUP 密集型任務,會阻塞消息隊列 21 22 s = 0 23 for i in range(1000000): # 模擬 CPU 密集型任務 24 s += i 25 26 print(f'{n} is done.') 27 28 return s 29 30 31 async def main(): 32 st = time.time() 33 tasks = [asyncio.create_task(work(i)) for i in range(3)] # 創建 3 個協程 34 35 await asyncio.gather(*tasks) # 並發啟動任務並等待任務結束 36 37 # 取消任務 38 for task in tasks: 39 task.cancel() 40 41 await asyncio.gather(*task, return_exceptions=True) 42 43 print('Done {:.2f}'.format(time.time()-st)) 44 45 46 if __name__ == '__main__': 47 asyncio.run(main())