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源创计划”,欢迎正在阅读的你也加入,一块儿分享。