gevent:异步理论与实战[转]

简介: 原创 2018-01-10 大邓 大邓带你玩python gevent库中使用的最核心的是Greenlet-一种用C写的轻量级python模块。在任意时间,系统只能允许一个Greenlet处于运行状态。

 

原创 2018-01-10 大邓 大邓带你玩python

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

谋胆并重
目录
相关文章
|
5月前
|
安全 Java 关系型数据库
深入探究Python的多线程与异步编程:实战与最佳实践
【2月更文挑战第1天】 深入探究Python的多线程与异步编程:实战与最佳实践
351 0
|
12月前
|
数据采集 存储 数据库
异步爬虫实战:实际应用asyncio和aiohttp库构建异步爬虫
异步爬虫实战:实际应用asyncio和aiohttp库构建异步爬虫
|
9天前
|
调度 Python
揭秘Python并发编程核心:深入理解协程与异步函数的工作原理
在Python异步编程领域,协程与异步函数成为处理并发任务的关键工具。协程(微线程)比操作系统线程更轻量级,通过`async def`定义并在遇到`await`表达式时暂停执行。异步函数利用`await`实现任务间的切换。事件循环作为异步编程的核心,负责调度任务;`asyncio`库提供了事件循环的管理。Future对象则优雅地处理异步结果。掌握这些概念,可使代码更高效、简洁且易于维护。
11 1
|
9天前
|
数据库 开发者 Python
实战指南:用Python协程与异步函数优化高性能Web应用
在快速发展的Web开发领域,高性能与高效响应是衡量应用质量的重要标准。随着Python在Web开发中的广泛应用,如何利用Python的协程(Coroutine)与异步函数(Async Functions)特性来优化Web应用的性能,成为了许多开发者关注的焦点。本文将从实战角度出发,通过具体案例展示如何运用这些技术来提升Web应用的响应速度和吞吐量。
12 1
|
3月前
|
数据处理 Python
深入探索:Python中的并发编程新纪元——协程与异步函数解析
【7月更文挑战第15天】Python 3.5+引入的协程和异步函数革新了并发编程。协程,轻量级线程,由程序控制切换,降低开销。异步函数是协程的高级形式,允许等待异步操作。通过`asyncio`库,如示例所示,能并发执行任务,提高I/O密集型任务效率,实现并发而非并行,优化CPU利用率。理解和掌握这些工具对于构建高效网络应用至关重要。
43 6
|
3月前
|
开发框架 并行计算 .NET
从菜鸟到大神:Python并发编程深度剖析,IO与CPU的异步战争!
【7月更文挑战第18天】Python并发涉及多线程、多进程和异步IO(asyncio)。异步IO适合IO密集型任务,如并发HTTP请求,能避免等待提高效率。多进程在CPU密集型任务中更优,因可绕过GIL限制实现并行计算。通过正确选择并发策略,开发者能提升应用性能和响应速度。
86 3
|
3月前
|
Python
从零到一:构建Python异步编程思维,掌握协程与异步函数
【7月更文挑战第15天】Python异步编程提升效率,通过协程与异步函数实现并发。从async def定义异步函数,如`say_hello()`,使用`await`等待异步操作。`asyncio.run()`驱动事件循环。并发执行任务,如`asyncio.gather()`同时处理`fetch_data()`任务,降低总体耗时。入门异步编程,解锁高效代码。
59 1
|
3月前
|
API 开发者 Python
从理论到实践,Python asyncio库让你成为异步编程的王者!
【7月更文挑战第11天】Python的asyncio库助力异步编程,通过事件循环实现非阻塞并发。定义async函数,如`fetch_url`,用await处理异步操作。在main函数中,利用`asyncio.gather`并发执行任务。进阶应用涉及并发控制(如`asyncio.Semaphore`)和异常处理,使asyncio成为高并发场景下的得力工具。开始探索,掌握asyncio,成为异步编程专家!
49 3
|
3月前
|
API 数据处理 Python
探秘Python并发新世界:asyncio库,让你的代码并发更优雅!
【7月更文挑战第11天】Python的asyncio库简化了单线程并发编程,利用协程和事件循环实现异步操作。async def定义异步函数,await挂起协程等待IO完成。例如,fetch_data模拟网络请求,main函数并发执行多个任务。asyncio.gather收集结果,Semaphore限制并发数,保证资源管理。asyncio提供高效优雅的并发解决方案。
50 4