Asyncio 使用經驗

Asyncio 使用經驗

4 人贊了文章

這是我在知乎上的第一篇文章。主要是介紹一下Python 3.4版本之後的Asyncio庫的使用,記錄一下自己的使用經驗。

什麼是Asyncio

這裡摘抄一段文檔中的說明

This module provides infrastructure for writing single-threaded concurrent code using coroutines, multiplexing I/O access over sockets and other resources, running network clients and servers, and other related primitives.

從名字上我們可以看到這個庫就是為了解決非同步IO而存在的。

在日常的使用中,我們會遇到的IO主要有:磁碟讀寫IO以及網路IO,使用asyncio可以幫助我們更加高效的使用資源,提高程序的響應,而不是讓程序順序排隊等在IO上。

如何使用Asyncio

在python中使用asyncio還是比較方便的,這裡摘抄一段文檔中的使用例子

import asynciodef hello_world(loop): print(Hello World) loop.stop()loop = asyncio.get_event_loop()# Schedule a call to hello_world()loop.call_soon(hello_world, loop)# Blocking call interrupted by loop.stop()loop.run_forever()loop.close()

這是文檔中的例子,這個例子在其他asyncio的文章中也是常見的啦。

當然在日常使用中,我們經常會調用run_until_complete()方法,稍微改造一下上面的例子:

import asyncioasync def hello_world(): print(Hello World) await asyncio.sleep(1)loop = asyncio.get_event_loop()loop.run_until_complete(hello_world())loop.close()

如果你使用的是3.4版本的python,請使用裝飾器來描述hello_world這個協程。

從上面的例子,有幾個關鍵點需要體現一下:

1、asyncio.get_event_loop()

2、用async描述的hello_world方法

3、loop.run_until_complete()

抽象的一個比喻:首先使用get_event_loop()方法獲取到一條傳輸帶,使用async(或者是裝飾器)包裝一下方法,丟到傳輸帶上,最後使用run_until_complete()(或者run_forever())開啟電源,讓這個傳輸帶工作起來。期間我們不需要管工人啥時候處理傳送帶上的方法,我們只要丟進去就好了。

這樣的比喻不知道貼切不貼切,這個是我認為比較好的一種理解asyncio的方式了。

經過上面的例子,我們依舊沒有感受到asyncio帶來的牛逼,調用一個hello_world還費那麼大勁。

那我們來點實際一些的例子(以下場景純屬YY):

小李接到上級任務:需要做一個爬蟲

沒錯,上級的任務就是這樣簡單粗暴,要做一個爬蟲。

小李是一個老搬磚工,一個爬蟲而已,輕車熟路啦,reqeusts請求,BeautifulSoup解析,最後保存數據完工。

但是小李這幾天看到qq群里有人在吹asyncio,他看完介紹之後覺得,這個東西,牛逼、酷、炫。加上項目不趕,所以他決定使用asyncio來實現這個爬蟲。

他分析了一下,決定使用多線程發起網路請求,多進程來解析dom,最後保存依舊使用多線程的方式來實現。

既然是老碼農,沒啥好說的,抄起鍵盤就是一頓操作,得出了如下代碼:

class XiaoLiSpider(object): def __init__(self) -> None: self._urls = [ "http://www.fover.cn/da/" "http://www.fover.cn/sha/" "http://www.fover.cn/bi/" ] self._loop = asyncio.get_event_loop() self._thread_pool = concurrent.futures.ThreadPoolExecutor( max_workers=10) self._process_pool = concurrent.futures.ProcessPoolExecutor() def crawl(self): a = [] for url in self._urls: a.append(self._little_spider(url)) self._loop.run_until_complete(asyncio.gather(*a)) self._loop.close() self._thread_pool.shutdown() self._process_pool.shutdown() async def _little_spider(self, url): response = await self._loop.run_in_executor( self._thread_pool, self._request, url) urls = await self._loop.run_in_executor(self._process_pool, self._biu_soup, response.text) self._save_data(urls) print(urls) def _request(self, url): return requests.get(url=url,timeout=10) @classmethod def _biu_soup(cls, response_str): soup = BeautifulSoup(response_str, lxml) a_tags = soup.find_all("a") a = [] for a_tag in a_tags: a.append(a_tag[href]) return a def _save_data(self,urls): #保存思路如上,這裡偷懶了大家根據實際需要寫 pass

沒錯就是這樣簡單的代碼實現了,多線程請求,多進程解析的操作,非常簡單,這讓我想起了Android開發中,使用rxAndroid可以方便的切換線程的快感。

扯遠了,回到小李的代碼,這裡有幾個點需要注意的

1、asyncio.gather(*a) 這裡將任務做個集合

2、run_in_executor()是一個好方法,可以方便的讓我們使用線程池和進程池

3、注意在使用進程池執行任務的時候,需要加上 @classmethod裝飾,因為多進程不共享內存,當然網上有更加詳細的解釋,大家可以上網搜一下具體的資料,我這裡簡答的描述為由於內存共享問題,所以多進程調用方法必須是無副作用的。

4、用完記得關。close()、shutdown() 牢記心間,要不然定時任務跑多了,你會發現一堆進程在那邊吃著cpu耗著memory在看戲。

加速Asyncio

小李寫完這個爬蟲之後,就向同組的大哥老王炫耀,老王,看了眼小李的代碼。

說到:吼啊!你這個asyncio用的還可以啊,不過還可以跑得更快。

小李:我當然是支持它跑得更快的。

老王:那我欽點uvloop,這個使用庫可以有效的加速asyncio,本庫基於libuv,也就是nodejs用的那個庫。github地址

MagicStack/uvloop?

github.com圖標

使用它也非常方便

import asyncioimport uvloopasyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

沒錯就是2行代碼,就可以提速asyncio,效果大概是這樣的:

小李看完捋了捋自己的頭髮,想著如果有一天自己的腦袋也像老王這樣油亮光滑,自己的編程水平也和他差不多了吧。

Asyncio生態

目前生態上,常見的非同步庫都有,由於我並沒有太多深入使用的經驗,所以我引用知乎上另一篇帖子,關於asyncio生態的討論。

截止到 2018 年 1 月,Python 的 asyncio 的生態如何??

www.zhihu.com圖標

最後說幾句

之前的例子我選用requests,因為很多程序員熟悉reqeusts這個庫,這個庫是阻塞庫,也就是傳統方式的類似BIO(Blocking IO)的方式,新的比較流行的網路請求庫是aiohttp,可以使用這個庫發起類似NIO(Not Blocking IO)的請求,但是相比reqeusts的流行度,大家還得再去熟悉aiohttp這一套,還是需要一些時間的學習成本的,使用run_in_executor() 方法可以在原有的基礎上寫非同步程序,當然肯定沒有aiohttp快,以及節省資源啦。


推薦閱讀:

為什麼 Python 中的複數形式是 (a + bj) 而不是 (a + bi) ?
Python 在 Linux 系統運維中都有哪些應用?
win8環境下python3.4怎麼樣配置才能把scrapy安裝成功?
1.1 用Python做數據化運營
專欄首發,序言

TAG:asyncio | Python | requests |