Python asyncio庫的學習和使用


因為要找工作,把之前自己搞的爬蟲整理一下,沒有項目經驗真蛋疼,只能做這種水的不行的東西。。。T  T,希望找工作能有好結果。

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

 

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

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

 

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

寫這篇博客的目的在於,網上看了一堆資料,自己還是沒有理解如何進行asyncio的編程,用到自己代碼里時候,還是有各種錯誤,因此打算看一下asyncio的官方手冊並好好梳理一下,希望時間的來的及~

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

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

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,這個程序的運行順序也很好理解

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

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的執行流程就比較清楚了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM