56

gevent:异步理论与实战

 6 years ago
source link: http://mp.weixin.qq.com/s/cPwJvN2Pe9bKI1Lguign0g
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Image

gevent库中使用的最核心的是Greenlet-一种用C写的轻量级python模块。在任意时间,系统只能允许一个Greenlet处于运行状态。那怎么让程序高并发,从而实现程序高效运行呢?

这就是我们常说的异步,在网络请求中,可以用下面的图清晰的看出异步的效率

串行和异步

高并发的核心是让一个大的任务分成一批子任务,并且子任务会被被系统高效率的调度,实现同步或者异步。在两个子任务之间切换,也就是经常说到的上下文切换。

同步就是让子任务串行,而异步有点影分身之术,但在任意时间点,真身只有一个,子任务并不是真正的并行,而是充分利用了碎片化的时间,让程序不要浪费在等待上。这就是异步,效率杠杆的。

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

这里我放一个动图,看看整个大的任务的调度顺序

Image

同步异步的顺序问题

同步运行就是串行,123456...,但是异步的顺序是随机的任意的(根据子任务消耗的时间而定)。

下面我们来看个代码

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()
Image

有道词典网站速度比较慢,基本上半秒解决一个词注释音标问题。那要是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()

这速度,酸爽啊

Image

速度与激情

6.44s vs 0.82s,让我们重新欣赏一会儿这两个动图

Image
Image

项目下载地址

链接: https://pan.baidu.com/s/1eT5gJrO 密码: wad8

数据采集

文本处理分析

图片数据处理

其他

Image
Image

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK