gevent庫中使用的最核心的是Greenlet-一種用C寫的輕量級python模塊。在任意時間,系統只能容許一個Greenlet處於運行狀態。那怎麼讓程序高併發,從而實現程序高效運行呢?
html
這就是咱們常說的異步,在網絡請求中,能夠用下面的圖清晰的看出異步的效率python
串行和異步
高併發的核心是讓一個大的任務分紅一批子任務,而且子任務會被被系統高效率的調度,實現同步或者異步。在兩個子任務之間切換,也就是常常說到的上下文切換。web
同步就是讓子任務串行,而異步有點影分身之術,但在任意時間點,真身只有一個,子任務並非真正的並行,而是充分利用了碎片化的時間,讓程序不要浪費在等待上。這就是異步,效率槓桿的。算法
gevent中的上下文切換是經過yield實現。在這個例子中,咱們會有兩個子任務,互相利用對方等待的時間作本身的事情。這裏咱們使用gevent.sleep(0)表明程序會在這裏停0秒。數據庫
import gevent
def foo(): print('Running in foo') gevent.sleep(0) print('Explicit context switch to foo again')
def bar(): print('Explicit context to bar') gevent.sleep(0) print('Implicit context switch back to bar')
gevent.joinall([ gevent.spawn(foo), gevent.spawn(bar),])
我谷歌了一下,spawn的意思是分支,這就很好的跟上面的那個圖對應起來,增強記憶。spawn-影分身之術。O(∩_∩)O~,讓待運行的任務切分紅更小的一批子任務。微信
下面咱們看看運行的順序:網絡
Running in foo Explicit context to bar Explicit context switch to foo again Implicit context switch back to bar
這裏我放一個動圖,看看整個大的任務的調度順序併發
同步異步的順序問題
同步運行就是串行,123456...,可是異步的順序是隨機的任意的(根據子任務消耗的時間而定)。echarts
下面咱們來看個代碼dom
import gevent
import random
def task(pid): """ Some non-deterministic task """ gevent.sleep(random.randint(0,2)*0.001) print('Task %s done' % pid)
#同步(結果更像串行)
def synchronous(): for i in range(1,10): task(i)
#異步(結果更像亂步)
def asynchronous(): threads = [gevent.spawn(task, i) for i in range(10)] gevent.joinall(threads)
print('Synchronous同步:')
synchronous()
print('Asynchronous異步:')
asynchronous()
Synchronous同步: Task 1 done Task 2 done Task 3 done Task 4 done Task 5 done Task 6 done Task 7 done Task 8 done Task 9 done Asynchronous異步: Task 1 done Task 5 done Task 6 done Task 2 done Task 4 done Task 7 done Task 8 done Task 9 done Task 0 done Task 3 done
同步案例中全部的任務都是按照順序執行,這致使主程序是阻塞式的(阻塞會暫停主程序的執行)。
gevent.spawn會對傳入的任務(子任務集合)進行進行調度,gevent.joinall方法會阻塞當前程序,除非全部的greenlet都執行完畢,程序纔會結束。
實戰
gevent以前寫過一期,但只是比較效率。這一期咱們要實現gevent到底怎麼用,怎麼把異步訪問獲得的數據提取出來。
最近作了個英語文本數據處理的任務,先作詞頻統計,而後對每一個詞語標註音標和註釋。其中標註音標和註釋,我沒有詞典,只能用爬蟲的方式訪問有道詞典,獲取想要的數據。
可是常規的for循環,word by word很慢,因而就想到用gevent。
分析url規律
首先抓包分析,打開開發者工具,清空訪問記錄。在有道詞典搜索框輸入「hello」按回車。觀察數據請求狀況 發現有道的url構建很簡單。
#url構建只須要傳入word便可 url = "http://dict.youdao.com/w/eng/{}/".format(word)
解析網頁數據
def fetch_word_info(word): url = "http://dict.youdao.com/w/eng/{}/".format(word) resp = requests.get(url,headers=headers) doc = pq(resp.text) pros = '' for pro in doc.items('.baav .pronounce'): pros+=pro.text() description = '' for li in doc.items('#phrsListTab .trans-container ul li'): description +=li.text() return {'word':word,'音標':pros,'註釋':description}
同步代碼
由於requests庫在任什麼時候候只容許有一個訪問結束徹底結束後,才能進行下一次訪問。沒法經過正規途徑拓展成異步,所以這裏使用了monkey補丁
import requests
from pyquery import PyQuery as pq
import gevent
import time
import gevent.monkey gevent.monkey.patch_all()
words = ['good','bad','cool', 'hot','nice','better', 'head','up','down', 'right','left','east']
def synchronous(): start = time.time() print('同步開始了') for word in words: print(fetch_word_info(word)) end = time.time() print("同步運行時間: %s 秒" % str(end - start))
#執行同步
synchronous()
有道詞典網站速度比較慢,基本上半秒解決一個詞註釋音標問題。那要是3600詞就須要半個小時,這速度坑啊!
異步代碼
由於requests庫在任什麼時候候只容許有一個訪問結束徹底結束後,才能進行下一次訪問。沒法經過正規途徑拓展成異步,所以這裏使用了monkey補丁
import requests
from pyquery import PyQuery as pq
import gevent
import time
import gevent.monkey gevent.monkey.patch_all()
words = ['good','bad','cool', 'hot','nice','better', 'head','up','down', 'right','left','east']
def asynchronous(): start = time.time() print('異步開始了') events = [gevent.spawn(fetch_word_info,word) for word in words] wordinfos = gevent.joinall(events) for wordinfo in wordinfos: #獲取到數據get方法 print(wordinfo.get()) end = time.time() print("異步運行時間: %s 秒"%str(end-start))
#執行異步
asynchronous()
這速度,酸爽啊
速度與激情
6.44s vs 0.82s,讓咱們從新欣賞一下子這兩個動圖
項目下載地址
連接: https://pan.baidu.com/s/1eT5gJrO 密碼: wad8
數據採集
文本處理分析
圖片數據處理
其餘
本文分享自微信公衆號 - 大鄧和他的Python(DaDengAndHisPython)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。