閱讀579 返回首頁    go 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

  上一篇:go 為什麼說 Python 是數據科學的發動機(一)發展曆程(附視頻中字
  下一篇:go 十張圖讀懂 PHP、Python、Ruby 三大語言的差異