Python進階:理解Python中的異步IO和協程(Coroutine),并應用在爬蟲中

duckbill 9年前發布 | 25K 次閱讀 IO Python Python開發

基礎知識

(1)什么是同步IO和異步IO,它們之間有什么區別?

答:舉個現實例子,假設你需要打開4個不同的網站,但每個網站都比較卡。IO過程就相當于你打開網站的過程,CPU就是你的點擊動作。你的點擊動作很快,但是網站打開很慢。同步IO是指你每點擊一個網址,都等待該網站徹底顯示,才會去點擊下一個網址。異步IO是指你點擊完一個網址,不等對方服務器返回結果,立馬新開瀏覽器窗口去打開另外一個網址,以此類推,最后同時等待4個網站徹底打開。很明顯異步IO的效率更高。

(2)什么是協程,為什么要使用協程?

Python中解決IO密集型任務(打開多個網站)的方式有很多種,比如多進程、多線程。但理論上一臺電腦中的線程數、進程數是有限的,而且進程、線程之間的切換也比較浪費時間。所以就出現了“協程”的概念。 協程允許一個執行過程A中斷,然后轉到執行過程B,在適當的時候再一次轉回來,有點類似于多線程。 但協程有以下2個優勢:

  • 協程的數量理論上可以是無限個,而且沒有線程之間的切換動作,執行效率比線程高。
  • 協程不需要“鎖”機制,即不需要lock和release過程,因為所有的協程都在一個線程中。
  • 相對于線程,協程更容易調試debug,因為所有的代碼是順序執行的。

Python中的異步IO和協程

Python中的協程是通過“生成器(generator)”的概念實現的。這里引用廖雪峰Python教程中的例子,并做一點修改和“裝飾”:

def consumer():         # 定義消費者,由于有yeild關鍵詞,此消費者為一個生成器
    print("[Consumer] Init Consumer ......")
    r = "init ok"       # 初始化返回結果,并在啟動消費者時,返回給生產者
    while True:
        n = yield r     # 消費者通過yield接收生產者的消息,同時返給其結果
        print("[Consumer] conusme n = %s, r = %s" % (n, r))
        r = "consume %s OK" % n     # 消費者消費結果,下個循環返回給生產者

def produce(c):         # 定義生產者,此時的 c 為一個生成器
    print("[Producer] Init Producer ......")
    r = c.send(None)    # 啟動消費者生成器,同時第一次接收返回結果
    print("[Producer] Start Consumer, return %s" % r)
    n = 0
    while n < 5:
        n += 1
        print("[Producer] While, Producing %s ......" % n)
        r = c.send(n)   # 向消費者發送消息并準備接收結果。此時會切換到消費者執行
        print("[Producer] Consumer return: %s" % r)
    c.close()           # 關閉消費者生成器
    print("[Producer] Close Producer ......")

produce(consumer())

代碼中添加了很詳細的print語句和注釋,幫助大家更好的理解。這里刪除了源代碼consumer中的“return”語句。如果還是不太明白,可以在編輯器中進行debug調試,一步步跟蹤程序的運行過程。

關于異步IO,在Python3.4中可以使用asyncio標準庫。該標準庫支持一個時間循環模型(EventLoop),我們聲明協程,然后將其加入到EventLoop中,即可實現異步IO。

Python中也有一個關于異步IO的很經典的HelloWorld程序(同樣參考于廖雪峰教程):

# 異步IO例子:適配Python3.4,使用asyncio庫
@asyncio.coroutine
def hello(index):                   # 通過裝飾器asyncio.coroutine定義協程
    print('Hello world! index=%s, thread=%s' % (index, threading.currentThread()))
    yield from asyncio.sleep(1)     # 模擬IO任務
    print('Hello again! index=%s, thread=%s' % (index, threading.currentThread()))

loop = asyncio.get_event_loop()     # 得到一個事件循環模型
tasks = [hello(1), hello(2)]        # 初始化任務列表
loop.run_until_complete(asyncio.wait(tasks))    # 執行任務
loop.close()                        # 關閉事件循環列表

同樣這里的代碼添加了注釋,并增加了index參數。輸出currentThread的目的是演示當前程序都是在一個線程中執行的。返回結果如下:

Hello world! index=1, thread=<_MainThread(MainThread, started 14816)>
Hello world! index=2, thread=<_MainThread(MainThread, started 14816)>
Hello again! index=1, thread=<_MainThread(MainThread, started 14816)>
Hello again! index=2, thread=<_MainThread(MainThread, started 14816)>

在Python3.5中引入了關于異步IO的新語法:async和await關鍵字。

# 異步IO例子:適配Python3.5,使用async和await關鍵字
async def hello(index):       # 通過關鍵字async定義協程
    print('Hello world! index=%s, thread=%s' % (index, threading.currentThread()))
    await asyncio.sleep(1)     # 模擬IO任務
    print('Hello again! index=%s, thread=%s' % (index, threading.currentThread()))

loop = asyncio.get_event_loop()     # 得到一個事件循環模型
tasks = [hello(1), hello(2)]        # 初始化任務列表
loop.run_until_complete(asyncio.wait(tasks))    # 執行任務
loop.close()                        # 關閉事件循環列表

從代碼中可以看出,使用async代替@asyncio.coroutine,使用await代替yield from,使得協程代碼更加簡潔易懂。

在爬蟲中使用協程實現異步IO

異步IO特別適合爬蟲的工作,因為爬蟲中所有的請求都屬于IO密集型任務,想得到比較好的爬蟲效率,使用協程很重要。關于Http異步請求,建議使用 aiohttp庫 ,一個異步的HTTP客戶端/服務器框架。這里舉個例子,更多用法可以參考其官方文檔。

async def get(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
print(url, resp.status)
print(url, await resp.text())

loop = asyncio.get_event_loop()     # 得到一個事件循環模型
tasks = [                           # 初始化任務列表
    get("http://zhushou.#/detail/index/soft_id/3283370"),
    get("http://zhushou.#/detail/index/soft_id/3264775"),
    get("http://zhushou.#/detail/index/soft_id/705490")
]
loop.run_until_complete(asyncio.wait(tasks))    # 執行任務
loop.close()                        # 關閉事件循環列表

 

 

來自:https://zhuanlan.zhihu.com/p/24118476

 

 本文由用戶 duckbill 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!