gevent学习
gevent
gevent基于协程的网络库,基于libev的快速的事件循环,基于greenlet的轻量级执行单元,重用了Python标准库中的概念,支持socket、ssl、三方库通过打猴子补丁的形式来提供同步方式编写代码的异步支持,dns解析通过线程池或c-ares,内置的tcp udp http服务器,支持多进程,线程池。
gevent和eventlet
2009年当时eventlet不支持livevent
当时eventlet的monkey-patch有bug导致socket操作会挂起
its Hub API is geared towards pure Python event loops. Making it work smoothly would require significant changes in the interface and implementation of every other hub. The socket module bugs were also specific to event loop, so even though I fixed them for pyevent, they were still present in every other hub.
gevent在libevent的基础上
当然最新的实现是使用了libev
libevent是事件循环,使用epoll、kqueue,dns是异步的,同时它提供了http服务器。而eventlet则是维持的纯python的事件循环(最近才支持epoll)。
除了效率,还集成了信号处理,其他使用libevent的库也可以集成进来,dns解析也可以异步了,wsgi服务器也是在libevent的自带服务器上运行的(很快)
和python标准的风格相同
例如线程的事件等都一样
不同点
没有eventlet.db_pool
没有多进程eventlet.processes
如果是在twister的reactor上使用
事件循环
gevent告诉操作系统等数据到达的时候来通知它,然后gevent就可以继续处理其他的协程,例如已经获得了数据的。
不像其他的网络库,gevent在一个协程中隐式启动循环,不用调用run或者dispatch什么的,当它要阻塞的时候,就会获取hub实例并切换到其中,也就是把控制权交给了hub,如果此时还没有hub就会自动创建一个。
切换规则
注意只有一个greenlet放弃了控制才能执行其他的(调用阻塞函数会切换回hub),这对于io频繁的系统当然没问题,但是对于cpu频繁的系统、或者是调用一些能绕过libev的函数。
锁、信号量什么的其实没什么用了,而event、asyncresult、queue等则还是有用的。
使用
创建一个Greenlet然后调用它的start函数,或者直接调用spawn函数。然后在当前协程放弃控制后会切换到并执行。如果greenlet执行时触发了异常,并不会超出greenlet的界限,只是打印stacktrace 默认是到stderror。
join等待该greenlet退出,kill中断该greenlet执行,get获得返回的值或re raise它产生的异常。
可以派生Greenlet,更改它的str方法来改变显示的traceback信息,但是得在初始化函数中先调用Greenlet.init(self)。
class MyNoopGreenlet(Greenlet):
def __init__(self, seconds):
Greenlet.__init__(self)
self.seconds = seconds
def _run(self):
gevent.sleep(self.seconds)
def __str__(self):
return 'MyNoopGreenlet(%s)' % self.seconds
kill可附带一个自定义的异常,但是可能该协程会捕获这个异常,可以附带timeout参数,也可以直接指定block=False来异步执行。
关于gevent的一点总结
0x00 基本概念
gevent是基于libev和greenlet的一个python异步框架。
libev是一个高性能的事件循环(event loop)实现。
事件循环(也称作IO多路复用),是解决阻塞问题,实现并发的一种方法。简单点说,就是event loop
会捕获、处理io
事件的变化:遇到阻塞,就跳出;阻塞结束,就继续。这依赖于系统底层的select
函数及其升级版:poll
和epoll
。《深入理解计算机系统》一书中,对此有深入探讨。
greenlet
是一个python
的协程管理、切换模块。通过greenlet
,你可以显式地在不同的任务之间切换。
0x01 Actor模式
Actor
是Erlang
语言的精髓。它强调基于消息传递的并发机制。
我们模仿这篇文章 建立一个actor模型。核心代码如下:
import genvet
# 主队列
queue = gevent.queue.JoinableQueue()
while True:
# 持续获取数据
try:
data = queue.get(timeout=5)
except Empty:
gevent.sleep(0.5)
continue
# 交付给Actor
if data:
gl = Actor(data)
g1.start()
g1.join()
Actor
为我们自定义的一个Greenlet
的子类,核心代码如下:
class Actor(Greenlet):
def __init__(self, data):
self.data = data
Greenlet.__init__(self)
def _run(self):
result = do_something(self.data)
return result
这样,我们这个Actor
可以从消息队列中接受数据,通过start()
,_run()
被调用。
由于gevent
和monkey patch
的存在,你基本可以以同步的方式,写出异步的代码。这样的写法显得很多余。但是,却可以很好的实现业务的分离,让代码更清晰,更容易维护和扩展。
0x02 实现回调
如果你还希望给Actor
加一个回调。即等他完成了之后,再进行某些处理。
那我们可以对Actor
机型如下修改:
class Actor(Greenlet):
def __init__(self, data):
self.data = data
Greenlet.__init__(self)
def _run(self):
# 通过self.link添加回调函数。
self.link(callback)
result = do_something(self.data)
return result
你可以通过Greenlet().link()
,给你的协程添加一个回调。这个回调函数只接收一个参数,即这个协程的实例(g1
)。我们会看到_run()
函数,其实是返回了一个结果result
的。那我们在回调函数中能否取得这个值呢。其实是可以的,这个值被存在了g1.value
中。
还是那句话,这样的设计,可能看起来很多余。但随着框架功能的增加,这样多余的设计会让你的代码,愈发地灵活。
学Python不得不掌握的库,gevent和asyncio使用方法详解
一、gevent
python程序实现的一种单线程下的多任务执行调度器,简单来说在一个线程里,先后执行AB两个任务,但是当A遇到耗时操作(网络等待、文件读写等),这个时候gevent会让A继续执行,但是同时也会开始执行B任务,如果B在遇到耗时操作同时A又执行完了耗时操作,gevent又继续执行A。
使用示例:
其中gevent.sleep()是gevent自带的延时,当gevent遇到这个延时时会自动切换,这里用gevent.sleep()代替一些耗时操作,如数据库的读写、http请求、文件度写等操作,gevent给我们提供了一个很简单的协程操作。
二、asyncio
asyncio的使用上,感觉和gevent有异曲同工之妙
1、基础概念:
event_loop事件循环:理解为一个循环的池,里面存放一些async关键词定义的协程函数,只有放到循环池里才能执行
coroutine协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
task任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。
future:代表将来执行或没有执行的任务的结果。它和task上没有本质的区别
async/await关键字:python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。
2、创建task
协程对象不能直接运行,需要包装成任务才能运行,上面是通过run_until_complete()方法包装成task(隐式包装),还有下面两种方式进行显式包装:
创建task后,task在加入事件循环之前是pending状态,加入loop后运行中是running状态,loop调用完是Done,运行完是finished状态,虽说本质上协程函数和task指的东西都一样,但是task有了协程函数的状态。
其中loop.run_until_complete()接受一个future参数,futurn具体指代一个协程函数,而task是future的子类,所以我们不声明一个task直接传入协程函数也能执行。
3、绑定回调函数
通过task的task.add_done_callback(callback)方法绑定回调函数,回调函数接收一个future对象参数如task,在内部通过future.result()获得协程函数的返回值。
4、await(挂起耗时操作)
多任务声明了协程函数,也同时在loop中注册了,他的执行也是顺序执行的,因为在异步函数中没有声明那些操作是耗时操作,所以会顺序执行。await的作用就是告诉控制器这个步骤是耗时的,async可以定义协程对象,使用await可以针对耗时的操作进行挂起
上面执行并不是异步执行,而是顺序执行,但是改成下面形式那就是异步执行:
可见三个任务的间隔时间几乎忽略不计,这里要注意可以使用await成功挂起的对应应该是下面三种:
原生异步函数(coroutine)
由 types.coroutine() 修饰的生成器,这个生成器可以返回 coroutine 对象。
包含 __await 方法的对象返回的一个迭代器
所以即使使用saync修饰requests的方法也不支持异步,而是需要专门的异步网络请求库aiohttp。
5、aiohttp
aiohttp需要单独安装,然后和asyncio库一起使用,看一下案例
几个任务的时间之差基本忽略不计,那亲测发送一千个请求也就11秒完成,确实很给力。
6、多进程配合使用
asyncio、aiohttp需要配合aiomultiprocess库使用,版本要求至少3.6,贴上该库的github上的使用示例,目前还在验证:
7、多协程并发
使用loop.run_until_complete(syncio.wait(tasks)) 也可以使用 loop.run_until_complete(asyncio.gather(*tasks)) ,前者传入task列表,会对task进行解包操作。
7、协程嵌套
顾名思义是一个协程中调用另一个协程,但是涉及到两个协程函数的结果处理和返回。
被调用协程返回结果有下列三种方式;
8、停止协程任务
实现结束task有两种方式:关闭单个task、关闭loop,涉及主要函数:
asyncio.Task.all_tasks()获取事件循环任务列表
KeyboardInterrupt捕获停止异常(Ctrl+C)
loop.stop()停止任务循环
task.cancel()取消单个任务
loop.run_forever()
loop.close()关闭事件循环,不然会重启
PYTHON GEVENT重要