Python高手進階|實戰4大併發祕籍

要點:

  • 手動線程池
  • concurrent.futures線程池
  • concurrent.futures進程池
  • gevent協程

1、實戰爬取維基百科例子

平時咱們有不少任務,尤爲是比較耗時的大量任務要處理,必定會用到併發處理。畢竟串行太慢了,下面咱們去爬一個維基百科的網站:python

輸入圖片說明

咱們來爬取紅框裏面的導航文本部分,這是一個很是簡單的爬蟲(關於爬蟲的文章前面寫的太多太多了,你們能夠翻歷史文章)
1).鏈接網頁

輸入圖片說明

2).爬取網頁

輸入圖片說明

  • 函數設計的時候咱們但願入參是一個元組(url,words),方便後面作併發處理
  • 網頁很是簡單,直接用requests取爬取,獲取text
  • 用pyquery來解析網頁,獲取對國家的描述
  • 數據結構用字典對來存儲

2、PK前作點準備工做

1).若是咱們如今要爬取100個國家的信息,有幾種辦法呢:
  • 最慢的串行爬取
  • 本身手動構建一個線程池,把要爬取的100國家都扔到共享隊列裏面,讓多個線程共享爬取
  • 利用concurrent.futures標準庫裏的線程池來爬去
  • 用多進程來爬取,雖然網頁請求是CPU密集型的,用進程有點浪費,可是咱們做爲對比,是能夠試一下的
  • 用協程也叫微線程,是一種綠色線程,用來作高併發很爽
2).爲了準確的計算每一種方法的耗時,咱們寫一個函數專門來計算時間:

輸入圖片說明

下面咱們用上面的5種方法逐一運行,爲了簡單期間咱們統一爬取5個國家,每種方法上面用裝飾器@cost_time來計算一下,看看到底哪一種方便比較簡單,速度又最快~~

3、慢慢的串行處理

先來段最通俗的one by one的串行處理

輸入圖片說明

>>>

輸入圖片說明

爬取5個國家的簡介花了17秒,串行由於在等待服務器的相應的時候傻等,因此浪費了不少時間

4、手動建多線程共享隊列

利用queue有鎖的功能,手動把數據塞進隊列,而後多個線程共享爬取

輸入圖片說明

>>>

輸入圖片說明

多線程確實很是快,只須要5秒左右就搞定了,可是用這種方法代碼太多,有沒有更優美的方法呢

5、用標準庫裏面的線程池

與其動手造輪子,不如用無所不能的庫,Python裏面的庫真的太多太多了!這也是Python爲啥這麼火爆的緣由之一.

輸入圖片說明

>>>

輸入圖片說明

發現用系統的線程池跟手動的幾乎差很少,可是你們發現沒有用輪子來處理,代碼量很是小,並且很優美!(這是Python之美,能用輪子儘可能用輪子,簡潔高效).

有同窗會問,有沒有什麼狀況是必定要手動構建線程池,而不能用 <ThreadPoolExecutor >,確實有這樣的狀況,你們思考一下,不明白的留言給我,偷偷告訴你.

6、用標準庫的裏進程池

既然上面有線程池,必定有進程池吧。是的,咱們下面來看看殺雞用牛刀的多進程處理,須要幾秒呢:

輸入圖片說明

>>>

輸入圖片說明

差很少也是5-6秒左右,多進程仍是比較快的!可是咱們這裏是5個國家,若是500個你不可能開100個進程來處理呢,若是碰到很是巨大的併發量,又要節省系統資源,又要速度很快,怎麼辦呢,咱們看最後一招

7、絕招,用協程來併發

用一下gevent這個庫,功能強大使用簡單,對協程的封裝比較好。每當一個協程阻塞時,程序將自動調度,gevent幫咱們處理了全部的底層細節

輸入圖片說明

gevent.spawn來建立一個一個的協程對象,而後joinall會等待全部的協程運行完成,最後咱們就獲取到幾個國家的簡介數據。
>>>

輸入圖片說明

哇協程果真很牛逼,只須要2.8秒左右,很是舒爽的感受!可是這裏由於requests庫有個缺點,訪問的時候是上一個訪問結束,才能進行下一次訪問!因此須要用gevent的猴子補丁.另外gevent雖然很好,可是它是大規模併發,若是發起10000個網絡請求,估計很快會被封IP!


好咱們來總結一下:

  • 同步處理:17秒
  • 異步手動建線程池:5.5秒
  • 異步標準庫線程池:5.2秒
  • 異步標準庫進程池:5.9秒
  • 併發協程:2.8秒
很明顯用Gevent最快,尤爲是在大規模的幾十萬級別的併發處理效果很是明顯.
線程池也是一個不錯的選擇,並且比較靈活,若是須要多個併發任務之間有交互的話,仍是須要用線程池.
那進程池呢,咱們上面考慮的都是IO 密集型task,若是咱們碰到了CPO型的那就必需要多核多CPU運行才能加速。畢竟Python有一個煩人的GIL,

好今天的文章就寫到這裏,歡迎留言討論服務器

文章摘自:菜鳥學python微信微信