Python 协程gevent
gevent是第三方库,通过greenlet实现协程,其基本思想是:
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。
由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成:
from gevent import monkey; monkey.patch_socket()
import gevent
import timedef f(n):for i in range(n):gevent.sleep(1)print gevent.getcurrent(), ig1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
运行结果:
<Greenlet at 0x2134d91b9d0: f(5)> 0
<Greenlet at 0x2134dd91e10: f(5)> 1
<Greenlet at 0x2134dd91bf0: f(5)> 2
<Greenlet at 0x2134d91b9d0: f(5)> 3
<Greenlet at 0x2134dd91e10: f(5)> 4
<Greenlet at 0x2134dd91bf0: f(5)> 0
<Greenlet at 0x2134d91b9d0: f(5)> 1
<Greenlet at 0x2134dd91e10: f(5)> 2
<Greenlet at 0x2134dd91bf0: f(5)> 3
<Greenlet at 0x2134d91b9d0: f(5)> 4
<Greenlet at 0x2134dd91e10: f(5)> 0
<Greenlet at 0x2134dd91bf0: f(5)> 1
<Greenlet at 0x2134d91b9d0: f(5)> 2
<Greenlet at 0x2134dd91e10: f(5)> 3
<Greenlet at 0x2134dd91bf0: f(5)> 4
可以看到,3个greenlet是依次运行而不是交替运行。
要让greenlet交替运行,可以通过gevent.sleep()
交出控制权:
def f(n):for i in range(n):print gevent.getcurrent(), igevent.sleep(0)
执行结果:
<Greenlet at 0x10cd58550: f(5)> 0
<Greenlet at 0x10cd58910: f(5)> 0
<Greenlet at 0x10cd584b0: f(5)> 0
<Greenlet at 0x10cd58550: f(5)> 1
<Greenlet at 0x10cd584b0: f(5)> 1
<Greenlet at 0x10cd58910: f(5)> 1
<Greenlet at 0x10cd58550: f(5)> 2
<Greenlet at 0x10cd58910: f(5)> 2
<Greenlet at 0x10cd584b0: f(5)> 2
<Greenlet at 0x10cd58550: f(5)> 3
<Greenlet at 0x10cd584b0: f(5)> 3
<Greenlet at 0x10cd58910: f(5)> 3
<Greenlet at 0x10cd58550: f(5)> 4
<Greenlet at 0x10cd58910: f(5)> 4
<Greenlet at 0x10cd584b0: f(5)> 4
3个greenlet交替运行,
把循环次数改为500000,让它们的运行时间长一点,然后在操作系统的进程管理器中看,线程数只有1个。
当然,实际代码里,我们不会用gevent.sleep()
去切换协程,而是在执行到IO操作时,gevent自动切换,代码如下:
from gevent import monkey; monkey.patch_all()
import gevent
import urllib2def f(url):print('GET: %s' % url)resp = urllib2.urlopen(url)data = resp.read()print('%d bytes received from %s.' % (len(data), url))gevent.joinall([gevent.spawn(f, 'https://www.python.org/'),gevent.spawn(f, 'https://www.yahoo.com/'),gevent.spawn(f, 'https://github.com/'),
])
运行结果:
GET: https://www.python.org/
GET: https://www.yahoo.com/
GET: https://github.com/
45661 bytes received from https://www.python.org/.
14823 bytes received from https://github.com/.
304034 bytes received from https://www.yahoo.com/.
从结果看,3个网络操作是并发执行的,而且结束顺序不同,但只有一个线程。
1 关于greenlet
greelet指的是使用一个任务调度器和一些生成器或者协程实现协作式用户空间多线程的一种伪并发机制,即所谓的微线程。
greelet机制的主要思想是:生成器函数或者协程函数中的yield语句挂起函数的执行,直到稍后使用next()或send()操作进行恢复为止。可以使用一个调度器循环在一组生成器函数之间协作多个任务。
网络框架的几种基本的网络I/O模型:
阻塞式单线程:这是最基本的I/O模型,只有在处理完一个请求之后才会处理下一个请求。它的缺点是效能差,如果有请求阻塞住,会让服务无法继续接受请求。但是这种模型编写代码相对简单,在应对访问量不大的情况时是非常适合的。
阻塞式多线程:针对于单线程接受请求量有限的缺点,一个很自然的想法就是给每一个请求开一个线程去处理。这样做的好处是能够接受更多的请求,缺点是在线程产生到一定数量之后,进程之间需要大量进行切换上下文的操作,会占用CPU大量的时间,不过这样处理的话编写代码的难道稍高于单进程的情况。
非阻塞式事件驱动:为了解决多线程的问题,有一种做法是利用一个循环来检查是否有网络IO的事件发生,以便决定如何来进行处理(reactor设计模式)。这样的做的好处是进一步降低了CPU的资源消耗。缺点是这样做会让程序难以编写,因为请求接受后的处理过程由reactor来决定,使得程序的执行流程难以把握。当接受到一个请求后如果涉及到阻塞的操作,这个请求的处理就会停下来去接受另一个请求,程序执行的流程不会像线性程序那样直观。twisted框架就是应用这种IO模型的典型例子。
非阻塞式Coroutine(协程):这个模式是为了解决事件驱动模型执行流程不直观的问题,它在本质上也是事件驱动的,加入了Coroutine的概念。
2 与线程/进程的区别
线程是抢占式的调度,多个线程并行执行,抢占共同的系统资源;而微线程是协同式的调度。
其实greenlet不是一种真正的并发机制,而是在同一线程内,在不同函数的执行代码块之间切换,实施“你运行一会、我运行一会”,并且在进行切换时必须指定何时切换以及切换到哪。greenlet的接口是比较简单易用的,但是使用greenlet时的思考方式与其他并发方案存在一定区别:
线程/进程模型在大逻辑上通常从并发角度开始考虑,把能够并行处理的并且值得并行处理的任务分离出来,在不同的线程/进程下运行,然后考虑分离过程可能造成哪些互斥、冲突问题,将互斥的资源加锁保护来保证并发处理的正确性。
greenlet则是要求从避免阻塞的角度来进行开发,当出现阻塞时,就显式切换到另一段没有被阻塞的代码段执行,直到原先的阻塞状况消失以后,再人工切换回原来的代码段继续处理。因此,greenlet本质是一种合理安排了的 串行 。
greenlet本质是串行,因此在没有进行显式切换时,代码的其他部分是无法被执行到的,如果要避免代码长时间占用运算资源造成程序假死,那么还是要将greenlet与线程/进程机制结合使用(每个线程、进程下都可以建立多个greenlet,但是跨线程/进程时greenlet之间无法切换或通讯)。
3 使用
一个 “greenlet” 是一个很小的独立微线程。可以把它想像成一个堆栈帧,栈底是初始调用,而栈顶是当前greenlet的暂停位置。你使用greenlet创建一堆这样的堆栈,然后在他们之间跳转执行。跳转不是绝对的:一个greenlet必须选择跳转到选择好的另一个greenlet,这会让前一个挂起,而后一个恢复。两 个greenlet之间的跳转称为 切换(switch) 。
当你创建一个greenlet,它得到一个初始化过的空堆栈;当你第一次切换到它,他会启动指定的函数,然后切换跳出greenlet。当最终栈底 函数结束时,greenlet的堆栈又编程空的了,而greenlet也就死掉了。greenlet也会因为一个未捕捉的异常死掉。
示例:来自官方文档示例
from greenlet import greenlet def test1(): print 12 gr2.switch() print 34 def test2(): print 56 gr1.switch() print 78 gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
最后一行跳转到 test1() ,它打印12,然后跳转到 test2() ,打印56,然后跳转回 test1() ,打印34,然后 test1() 就结束,gr1死掉。这时执行会回到原来的 gr1.switch() 调用。注意,78是不会被打印的,因为gr1已死,不会再切换。
4 基于greenlet的框架
4.1 eventlet
eventlet 是基于 greenlet 实现的面向网络应用的并发处理框架,提供“线程”池、队列等与其他 Python 线程、进程模型非常相似的 api,并且提供了对 Python 发行版自带库及其他模块的超轻量并发适应性调整方法,比直接使用 greenlet 要方便得多。
其基本原理是调整 Python 的 socket 调用,当发生阻塞时则切换到其他 greenlet 执行,这样来保证资源的有效利用。需要注意的是:
eventlet 提供的函数只能对 Python 代码中的 socket 调用进行处理,而不能对模块的 C 语言部分的 socket 调用进行修改。对后者这类模块,仍然需要把调用模块的代码封装在 Python 标准线程调用中,之后利用 eventlet 提供的适配器实现 eventlet 与标准线程之间的协作。
虽然 eventlet 把 api 封装成了非常类似标准线程库的形式,但两者的实际并发执行流程仍然有明显区别。在没有出现 I/O 阻塞时,除非显式声明,否则当前正在执行的 eventlet 永远不会把 cpu 交给其他的 eventlet,而标准线程则是无论是否出现阻塞,总是由所有线程一起争夺运行资源。所有 eventlet 对 I/O 阻塞无关的大运算量耗时操作基本没有什么帮助。
4.2 gevent
4.2.1 gevent是一个基于协程(coroutine)的Python网络函数库,通过使用greenlet提供了一个在libev事件循环顶部的高级别并发API。
主要特性有以下几点:
基于libev的快速事件循环,Linux上面的是epoll机制
基于greenlet的轻量级执行单元
API复用了Python标准库里的内容
支持SSL的协作式sockets
可通过线程池或c-ares实现DNS查询
通过monkey patching功能来使得第三方模块变成协作式
ps:
1、关于Linux的epoll机制:
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。epoll的优点:
支持一个进程打开大数目的socket描述符。select的一个进程所打开的FD由FD_SETSIZE的设置来限定,而epoll没有这个限制,它所支持的FD上限是最大可打开文件的数目,远大于2048。
IO效率不随FD数目增加而线性下降:由于epoll只会对“活跃”的socket进行操作,于是,只有”活跃”的socket才会主动去调用 callback函数,其他idle状态的socket则不会。
使用mmap加速内核与用户空间的消息传递。epoll是通过内核于用户空间mmap同一块内存实现的。
内核微调。
2、libev机制
提供了指定文件描述符事件发生时调用回调函数的机制。libev是一个事件循环器:向libev注册感兴趣的事件,比如socket可读事件,libev会对所注册的事件的源进行管理,并在事件发生时触发相应的程序。
4.2.2 官方文档中的示例:
import geventfrom gevent import socketurls = [‘www.google.com.hk’,’www.example.com’, ‘www.python.org’ ]jobs = [gevent.spawn(socket.gethostbyname, url) for url in urls]gevent.joinall(jobs, timeout=2)[job.value for job in jobs]
[‘74.125.128.199’, ‘208.77.188.166’, ‘82.94.164.162’]
注解:gevent.spawn()方法spawn一些jobs,然后通过gevent.joinall将jobs加入到微线程执行队列中等待其完成,设置超时为2秒。执行后的结果通过检查gevent.Greenlet.value值来收集。gevent.socket.gethostbyname()函数与标准的socket.gethotbyname()有相同的接口,但它不会阻塞整个解释器,因此会使得其他的greenlets跟随着无阻的请求而执行。
4.2.3 Monket patching
Python的运行环境允许我们在运行时修改大部分的对象,包括模块、类甚至函数。虽然这样做会产生“隐式的副作用”,而且出现问题很难调试,但在需要修改Python本身的基础行为时,Monkey patching就派上用场了。Monkey patching能够使得gevent修改标准库里面大部分的阻塞式系统调用,包括socket,ssl,threading和select等模块,而变成协作式运行。
from gevent import monkey ;monkey . patch_socket ()import urllib2
通过monkey.patch_socket()方法,urllib2模块可以使用在多微线程环境,达到与gevent共同工作的目的。
4.2.4 事件循环
不像其他网络库,gevent和eventlet类似, 在一个greenlet中隐式开始事件循环。没有必须调用run()或dispatch()的反应器(reactor),在twisted中是有 reactor的。当gevent的API函数想阻塞时,它获得Hub实例(执行时间循环的greenlet),并切换过去。如果没有集线器实例则会动态 创建。
libev提供的事件循环默认使用系统最快轮询机制,设置LIBEV_FLAGS环境变量可指定轮询机制。LIBEV_FLAGS=1为select, LIBEV_FLAGS = 2为poll, LIBEV_FLAGS = 4为epoll,LIBEV_FLAGS = 8为kqueue。
Libev的API位于gevent.core下。注意libev API的回调在Hub的greenlet运行,因此使用同步greenlet的API。可以使用spawn()和Event.set()等异步API。
Python 协程gevent相关推荐
- python协程gevent案例:爬取斗鱼美女图片
分析 分析网站寻找需要的网址 用谷歌浏览器摁F12打开开发者工具,然后打开斗鱼颜值分类的页面,如图: 在里面的请求中,最后发现它是以ajax加载的数据,数据格式为json,如图: 圈住的部分是我们需要 ...
- python协程gevent案例 爬取斗鱼图片过程解析 - python
文章来源: 敏而好学论坛 嗨学网www.piaodoo.com 欢迎大家相互学习 分析 分析网站寻找需要的网址 用谷歌浏览器摁F12打开开发者工具,然后打开斗鱼颜值分类的页面,如图: 在里面的请求中, ...
- python协程gevent monkey的MonkeyPatchWarning 警告
协程 猴子修补的MonkeyPatchWarning 警告 MonkeyPatchWarning: Monkey-patching ssl after ssl has already been imp ...
- python 协程 gevent
第三方协程模 greenlet模块 示例代码: day12/greenlet_0.py 安装 : sudo pip3 install greenlet 函数 greenlet.greenlet(fun ...
- Python并发之协程gevent基础
基本示例 from gevent import monkey monkey.patch_all() # 记住一定放在第一行,这里是打补丁的意思,time模块在使用协程gevent模块的时候,必须打补丁 ...
- python gevent async_谈谈Python协程技术的演进
原标题:谈谈Python协程技术的演进 Coding Crush Python开发工程师 主要负责岂安科技业务风险情报系统redq. 引言 1.1. 存储器山 存储器山是 Randal Bryant ...
- c++ 协程_理解Python协程(Coroutine)
由于GIL的存在,导致Python多线程性能甚至比单线程更糟. GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种 ...
- python 协程原理_Python协程greenlet实现原理
greenlet是stackless Python中剥离出来的一个项目,可以作为官方CPython的一个扩展来使用,从而支持Python协程.gevent正是基于greenlet实现. 协程实现原理 ...
- python 协程、进程、线程_Python 中的进程、线程、协程
1. 进程 进程是正在运行的程序实例,是内核分配资源的最基本的单元.进程拥有自己独立的堆和栈,独立的地址空间,资源句柄.进程由 OS 调度,调度开销较大,在并发的切换过程效率较低. Python 提供 ...
最新文章
- JSP中include指令的乱码问题
- boost::endian模块实现reverse的测试程序
- 一个农场有头母牛,现在母牛才一岁,要到四岁才能生小牛,四岁之后,每年生一头小牛,n年后有多少头小牛
- SpringBoot @Resource注入的坑
- java实现c语言的函数_C语言实现返回字符串函数的四种方法
- tftp服务器从交换机上下载配置命令_软考网络工程师之交换机和路由(交换机基础)...
- 不定高宽的元素居中的方法
- BST:Transformer在推荐领域的应用
- thinkphp3.2 不同域名配置不同分组设置
- optimizer_mode优化器模式
- 1、爱因斯相对论(狭义相对论)
- pandas小记:pandas汇总统计函数
- Hibernate VS iBATIS (转自ITEYE davy)
- uni项目中如何实现微信小程序文件下载(包会)
- 软件能力成熟度模型(Capabilitymaturity model,CMM)
- AutoHotKey写一个改键的小脚本
- 如何防止跨站点脚本攻击
- ISPO Beijing 2019盛大开幕,双驰个性化定制设备强势参展,人气爆棚
- bat批处理文件无法执行
- Gitee崩了?大量仓库被关闭,官方答复“迫于无奈”
热门文章
- 120分的转录组试题,你能得多少
- 在Mac上唤出「快速备忘录」的开启与关闭设置教程
- python gui界面实例_Python界面(GUI)编程PyQt5工具栏和菜单
- 如何确认自己的电脑是否可以更新到win11
- 功能强大的JavaScript 拖拽库 SortableJS
- Qt实践| HTTP知识点-接入某图片验证码系统识别验证码
- Qt文档阅读笔记-QNetworkAccessManager官方解析与实例
- Qt工作笔记-QTabWidget插入widget(重要的是编程思想)
- c语言图案问题,C语言绘图问题
- ncl如何添加线shp文件_NCL画图个例讲解