579
Python
Python3異步編程
Python3.4之後引入asyncio標準庫,並在3.5中提供原生語法支持,為編寫異步程序提供了高效且優雅的方法。對於編寫爬蟲和httpserver的這類IO密集型應用,asyncio的表現非常亮眼。
asyncio基於協程實現,至於為什麼不用進程or線程實現並發,忽略內核陷入開銷以及GIL,進程與線程依賴操作係統調度,調度開銷高,調度方式也不一定與應用適配。
Coroutine協程
協程是可以暫停和恢複的用戶態“線程”。但協程的定義並不十分明確,也有多種實現。但總的來講都是基於生成器,所以為了理解協程,先簡單介紹一下python生成器:
1.棧幀PyFrameObject保存代碼的信息和上下文
2.棧幀擁有自己的數據棧和block棧,解釋器可以中斷和恢複棧幀
3.python將函數編譯成字節碼時,碰到yield語句,標記它為生成器函數
4.程序調用生成器函數時,python創建生成器對象
5.所有調用特定生成器函數得到的生成器對象都指向同樣的代碼。但每個生成器對象都有獨立的棧幀
6.調用send或next方法,由gen_send_ex函數執行係列操作、修改生成器狀態,然後調用PyEval_EvalFrameEx執行字節碼(如果代碼塊為空或調用棧為空,拋出StopIteration異常)
EventLoop 事件循環
事件循環是asyncio的核心,它負責:
1.注冊、執行以及取消超時調用
2.為各種通信創建client和server通道
3.啟動子程序和並創建與外部程序通信的通道
4.將耗時任務委托給線程池
獲取事件循環:
asyncio.get_event_loop_policy獲取事件循環策略,asyncio.get_event_loop獲取事件循環.
設置事件循環:
asyncio.set_event_loop_policy設置事件循環策略,asyncio.set_event_loop設置事件循環
以uvloop為例:
輸出:
開啟事件循環:
AbstractEventLoop.run_forever開啟事件循環直到stop方法被調用
AbstractEventLoop.run_until_complete開啟事件循環,如果參數是協程對象,ensure_future會將協程封裝為Task。函數返回Futures的結果或者拋出相應的異常。
關閉事件循環:
AbstractEventLoop.stop如果run_forever正在運行,對當前批次執行callback後退出。
run_forever再次被調用時,繼續執行。
AbstractEventLoop.close強製關閉事件循環,拋棄待處理的回調。並且關閉之後不可逆轉。
Future
封裝callable的異步執行,Task的基類,非線程安全。
Task任務
Task是Future的子類,負責在事件循環中執行協程。事件循環同一時間隻執行一個task(其他線程中的任務可能可以“並行”)。當task等待其他future完成時,事件循環執行新的task(並發)。
使用asyncio.ensure_future函數或者AbstractEventLoop.create_task方法創建task,Task非線程安全。
對於非線程安全問題,一個event_loop隻在一個線程中運行,所以隻需要擔心event_loop之外的情況,asyncio.run_coroutine_threadsafe與loop.call_soon_threadsafe可以應對。
Crawler
接下來我們基於asyncio和aiohttp編寫一個爬蟲抓取豆瓣電影top250(隱去異常處理,下同):
線程池版本:
這個對比雖不夠嚴謹,但也可以看出asyncio的異步性能,特別是在並發量越來越大的時候,python可以輕鬆開啟上萬的協程,而開啟上萬線程操作係統可能會不堪重負。
結語
上麵我們介紹了協程和asyncio標準庫,並編寫了一個簡單的網絡爬蟲,對比協程和多線程版本。實際上對於HTTPSERVER,Sanic+uvloop能創造出驚人的性能,詳見uvloop:Blazing fast Python networking(
https://magic.io/blog/uvloop-blazing-fast-python-networking/)
最後更新:2017-10-10 19:44:33