zoukankan      html  css  js  c++  java
  • gevent:异步理论与实战[转]

     

    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

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

    同步异步的顺序问题

    同步运行就是串行,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()

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

  • 相关阅读:
    POJ 3037 Skiing(Dijkstra)
    HDU 1875 畅通工程再续(kruskal)
    HDU 1233 还是畅通工程(Kruskal)
    Java实现 LeetCode 754 到达终点数字(暴力+反向)
    Java实现 LeetCode 754 到达终点数字(暴力+反向)
    Java实现 LeetCode 754 到达终点数字(暴力+反向)
    Java实现 LeetCode 753 破解保险箱(递归)
    Java实现 LeetCode 753 破解保险箱(递归)
    Java实现 LeetCode 753 破解保险箱(递归)
    Java实现 LeetCode 752 打开转盘锁(暴力)
  • 原文地址:https://www.cnblogs.com/sunsky303/p/8309201.html
Copyright © 2011-2022 走看看