1. 程式人生 > >異步協程太吊了!以親測!簡直完美,Python異步協程的葵花寶典!

異步協程太吊了!以親測!簡直完美,Python異步協程的葵花寶典!

獲取 nis 時間 定義 換上 req 體驗 用戶輸入 3.1

技術分享圖片

2.1 阻塞

進群:125240963 即可獲取數十套PDF哦!

阻塞狀態指程序未得到所需計算資源時被掛起的狀態。程序在等待某個操作完成期間,自身無法繼續幹別的事情,則稱該程序在該操作上是阻塞的。

常見的阻塞形式有:網絡 I/O 阻塞、磁盤 I/O 阻塞、用戶輸入阻塞等。阻塞是無處不在的,包括 CPU 切換上下文時,所有的進程都無法真正幹事情,它們也會被阻塞。如果是多核 CPU 則正在執行上下文切換操作的核不可被利用。

技術分享圖片

技術分享圖片

2.5 多進程

多進程就是利用 CPU 的多核優勢,在同一時間並行地執行多個任務,可以大大提高執行效率。

2.6 協程

協程,英文叫做 Coroutine,又稱微線程,纖程,協程是一種用戶態的輕量級線程。

技術分享圖片

3.1 定義協程

首先我們來定義一個協程,體驗一下它和普通進程在實現上的不同之處,代碼如下:

技術分享圖片

首先我們引入了 asyncio 這個包,這樣我們才可以使用 async 和 await,然後我們使用 async 定義了一個 execute() 方法,方法接收一個數字參數,方法執行之後會打印這個數字。

技術分享圖片

技術分享圖片

這裏我們定義了 loop 對象之後,接著調用了它的 create_task() 方法將 coroutine 對象轉化為了 task 對象,隨後我們打印輸出一下,發現它是 pending 狀態。接著我們將 task 對象添加到事件循環中得到執行,隨後我們再打印輸出一下 task 對象,發現它的狀態就變成了 finished,同時還可以看到其 result 變成了 1,也就是我們定義的 execute() 方法的返回結果。

技術分享圖片

發現其效果都是一樣的。

3.2 綁定回調

另外我們也可以為某個 task 綁定一個回調方法,來看下面的例子:

技術分享圖片

在這裏我們定義了一個 request() 方法,請求了百度,返回狀態碼,但是這個方法裏面我們沒有任何 print() 語句。隨後我們定義了一個 callback() 方法,這個方法接收一個參數,是 task 對象,然後調用 print() 方法打印了 task 對象的結果。這樣我們就定義好了一個 coroutine 對象和一個回調方法,我們現在希望的效果是,當 coroutine 對象執行完畢之後,就去執行聲明的 callback() 方法。

技術分享圖片

3.3 多任務協程

上面的例子我們只執行了一次請求,如果我們想執行多次請求應該怎麽辦呢?我們可以定義一個 task 列表,然後使用 asyncio 的 wait() 方法即可執行,看下面的例子:

技術分享圖片

可以看到五個任務被順次執行了,並得到了運行結果。

3.4 協程實現

前面說了這麽一通,又是 async,又是 coroutine,又是 task,又是 callback,但似乎並沒有看出協程的優勢啊?反而寫法上更加奇怪和麻煩了,別急,上面的案例只是為後面的使用作鋪墊,接下來我們正式來看下協程在解決 IO 密集型任務上有怎樣的優勢吧!

技術分享圖片

這裏我們定義了一個 Flask 服務,主入口是 index() 方法,方法裏面先調用了 sleep() 方法休眠 3 秒,然後接著再返回結果,也就是說,每次請求這個接口至少要耗時 3 秒,這樣我們就模擬了一個慢速的服務接口。

技術分享圖片

可以發現和正常的請求並沒有什麽兩樣,依然還是順次執行的,耗時 15 秒,平均一個請求耗時 3 秒,說好的異步處理呢?

其實,要實現異步處理,我們得先要有掛起的操作,當一個任務需要等待 IO 結果的時候,可以掛起當前任務,轉而去執行其他任務,這樣我們才能充分利用好資源,上面方法都是一本正經的串行走下來,連個掛起都沒有,怎麽可能實現異步?想太多了。

技術分享圖片

reqeusts 返回的 Response 不符合上面任一條件,因此就會報上面的錯誤了。

那麽有的小夥伴就發現了,既然 await 後面可以跟一個 coroutine 對象,那麽我用 async 把請求的方法改成 coroutine 對象不就可以了嗎?所以就改寫成如下的樣子:

技術分享圖片

還是不行,它還不是異步執行,也就是說我們僅僅將涉及 IO 操作的代碼封裝到 async 修飾的方法裏面是不可行的!我們必須要使用支持異步操作的請求方式才可以實現真正的異步,所以這裏就需要 aiohttp 派上用場了。

3.5 使用 aiohttp

aiohttp 是一個支持異步請求的庫,利用它和 asyncio 配合我們可以非常方便地實現異步請求操作。

安裝方式如下:

技術分享圖片

成功了!我們發現這次請求的耗時由 15 秒變成了 3 秒,耗時直接變成了原來的 1/5。

代碼裏面我們使用了 await,後面跟了 get() 方法,在執行這五個協程的時候,如果遇到了 await,那麽就會將當前協程掛起,轉而去執行其他的協程,直到其他的協程也掛起或執行完畢,再進行下一個協程的執行。

技術分享圖片

最後運行時間也是在 3 秒左右,當然多出來的時間就是 IO 時延了。

可見,使用了異步協程之後,我們幾乎可以在相同的時間內實現成百上千倍次的網絡請求,把這個運用在爬蟲中,速度提升可謂是非常可觀了。

3.6 與單進程、多進程對比

可能有的小夥伴非常想知道上面的例子中,如果 100 次請求,不是用異步協程的話,使用單進程和多進程會耗費多少時間,我們來測試一下:

首先來測試一下單進程的時間:

技術分享圖片

可見 multiprocessing 相比單線程來說,還是可以大大提高效率的。

3.7 與多進程的結合

既然異步協程和多進程對網絡請求都有提升,那麽為什麽不把二者結合起來呢?在最新的 PyCon 2018 上,來自 Facebook 的 John Reese 介紹了 asyncio 和 multiprocessing 各自的特點,並開發了一個新的庫,叫做 aiomultiprocess,感興趣的可以了解下:https://www.youtube.com/watch?v=0kXaLh8Fz3k。

這個庫的安裝方式是:

技術分享圖片

技術分享圖片

異步協程太吊了!以親測!簡直完美,Python異步協程的葵花寶典!