Python asyncio庫的學習和使用

由於要找工做,把以前本身搞的爬蟲整理一下,沒有項目經驗真蛋疼,只能作這種水的不行的東西。。。T  T,但願找工做能有好結果。html

以前爬蟲使用的是requests+多線程/多進程,後來隨着前幾天的深刻了解,才發現,對於爬蟲來講,真正的瓶頸並非CPU的處理速度,而是對於網頁抓取時候的往返時間,由於若是採用requests+多線程/多進程,他自己是阻塞式的編程,因此時間都花費在了等待網頁結果的返回和對爬取到的數據的寫入上面。而若是採用非阻塞編程,那麼就沒有這個困擾。這邊首先要理解一下阻塞和非阻塞的區別python

 

1.阻塞調用是指調用結果返回以前,當前線程會被掛起(線程進入非可執行狀態,在這個狀態下,CPU不會給線程分配時間片,即線程暫停運行)。函數只有在獲得結果以後纔會返回。編程

2.對於非阻塞則不會掛起,直接執行接下去的程序,返回結果後再回來處理返回值。多線程

 

python 3.4開始就支持異步IO編程,提供了asyncio庫,可是3.4中採用的是@asyncio.coroutine和yield from這和原來的generator關鍵字yield很差區分,在3.5中,採用了async(表示攜程)和await關鍵字,這樣就好區分多了。異步

寫這篇博客的目的在於,網上看了一堆資料,本身仍是沒有理解如何進行asyncio的編程,用到本身代碼裏時候,仍是有各類錯誤,所以打算看一下asyncio的官方手冊並好好梳理一下,但願時間的來的及~async

https://docs.python.org/3/library/asyncio.html函數

這邊用一個簡單的官方例子來講明async和await的執行順序。oop

import asyncio

async def compute(x, y):
    print("Compute %s + %s ..." % (x, y))
    await asyncio.sleep(1.0)
    return x + y

async def print_sum(x, y):
    result = await compute(x, y)
    print("%s + %s = %s" % (x, y, result))

loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()

若是不使用async和await,這個程序的運行順序也很好理解spa

1.print_sum(1,2)將參數1,2傳遞給函數print_sum.net

2.先執行第一句,result = compute(x,y),

3.將1,2傳遞給compute函數,compute函數收到參數

4.先執行 print("Compute %s + %s ..." % (x, y))打印出Compute 1 + 2 ...

5.執行sleep(1.0)程序掛起一秒

6.返回1+2的值3

7.print_sum的result收到返回值3,執行print("%s + %s = %s" % (x, y, result)),打印出1 + 2 = 3

8.程序結束

若是採用異步的話,他執行順序是怎麼樣的呢?

下圖是他官方文檔的說明:

 

要理解上圖,首先咱們要理解Event_loop,feture,task等概念,詳細的能夠參考官方文檔或者http://my.oschina.net/lionets/blog/499803

 首先get_event_loop()方法讓咱們獲得一個消息環,event loop對象包括兩部分:event和loop,event負責I/O時間通知二loop負責循環處理I/O通知並在就緒時調用回調。

event loop 內部維護着兩個容器:_ready 和 _scheduled。類型分別是 deque 和 list。_ready 表明已經能夠執行,_scheduled 表明計劃執行。_scheduled 中的 handle 是能夠 cancel 的。

要理解task,首先須要理解future對象(via http://my.oschina.net/lionets/blog/499803)

class asyncio.Future(*, loop=None) 是對一個可調用對象的異步執行控制或者說代理的封裝。所以具備以下方法:

  • cancel()
  • cancelled()
  • done()
  • result()
  • exception()
  • add_done_callback(fn)
  • remove_done_callback(fn)
  • set_result(result)
  • set_exception(exception)

注意 Future 並不包含可執行對象的本體,他只保存狀態、結果、額外的回調函數這些東西。這也是上面稱之爲代理的緣由。由於實際的調用過程是在 event loop 裏發生的,event loop 負責在異步執行完成後向 future 對象寫入 result 或 exception。這是異步任務的基本邏輯。

future 實例有三種狀態:

  • PENDING
  • CANCELLED
  • FINISHED

初始狀態爲 PENDING,當調用 cancel() 方法時會當即進入 CANCELLED 狀態並 schedule callbacks。當被調用 set_result()時會進入 FINISHED 狀態,並 schedule callbacks。固然兩種狀況下傳入 callback 的參數會不一樣。

Task是 Future 的子類。由於 Future 沒有保存其相關可執行對象的信息,咱們 schedule the execution of a coroutine 這件事通常是經過 Task 對象來作的。 Task 提供了一種機制用於當 future 完成時喚醒父級協程。即爲當 await future 時,task 對象會將此 future 保存到 _fut_waiter 對象中,併爲其添加一個名爲_wake_up() 的回調。

繼續分析上面這段代碼,首先async def建立一個coroutine object,get_event_loop建立一個消息環,接下去,run_until_complete()接受coroutine object對象,經過隱式轉換(ensure_future())將其包裝成一個future,這邊是一個task對象(內部實現機制我其實也並非很是肯定,大體是這樣,好比如何從future轉換成task??)

1.get_event_loop()建立消息環

2.print_sum返回一個攜程對象(coroutine object)

3.print_sum(1,2)接受參數

4.result = await compute(x,y),此時程序掛起,直接調用compute(1,2)

5.打印出Compute 1 + 2 ...

6.執行sleep(1.0) 因爲使用await,因此sleep(1.0)被掛起,直接執行下一句

7.這邊因爲只有一個task,即print_sum(1,2),所以沒有其餘task能夠執行,因此result等待compute(x,y)的返回值,等待1秒之後,返回1+2的值3

而後執行print("%s + %s = %s" % (x, y, result))

其實上面的程序可能還不能很明白async def和 await的調用過程究竟是怎麼樣,稍微修改一下上面的程序就能更加明白他的執行過程了。

import asyncio

async def compute(x, y):
    print("Compute %s + %s ..." % (x, y))
    await asyncio.sleep(10.0)
    return x + y

async def print_sum(x, y):
    result = await compute(x, y)
    print("%s + %s = %s" % (x, y, result))

loop = asyncio.get_event_loop()
tasks = [print_sum(1,2),print_sum(3,4)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

把代碼修改爲這樣,其餘不變,多了一個task,爲了更加直觀,我把sleep(1.0)改爲了sleep(10.0)

此時程序的執行結果是

Compute 3 + 4 ...
Compute 1 + 2 ...
暫停10秒左右
3 + 4 = 7
1 + 2 = 3

很是須要注意的就是這邊暫停的時間,是10秒左右,不是20秒,因此他執行順序是對於task

先執行print_sum(3,4)(這邊tasks額執行順序我不是很是理解,我多加了不少task測驗,發現都是從第二個開始執行,而後最後執行第一個task,沒懂是爲何)將3,4扔給compute,在compute中,sleep(10.0)掛起,此時不等待sleep(10.0)執行完畢,直接執行tasks中的另外一個task,print_sum(1,2),將1,2扔給compute,在compute中,sleep(10.0)掛起,因爲已經沒有其餘的task,因此等待第一個sleep(10.0)執行完畢之後返回3+4的結果爲7,執行result = await compute(3, 4)後面的程序,即打印出3 + 4 = 7執行完畢之後,第二個sleep(10.0)也差很少返回了,所以回到原來掛起的result = await compute(1, 2),打印出1 + 2 = 3.全部task執行完畢,loop complete而且close。

這樣整個async def 和await的執行流程就比較清楚了。

相關文章
相關標籤/搜索