python编程中,我们使用到的并发框架主要有以下几种
一、并发框架
greenlet、eventlet、gevnet(推荐)
- greenlet:轻量级的并行编程,调度麻烦,用生成器实现的协程而且不是真正意义上的协程,只是实现代码执行过程中的挂起、唤醒操作。Greenlet 没有自己的调度过程,所以一般不会直接使用。greenlet:http://greenlet.readthedocs.org/en/latest/
- eventlet:是在 greenlet 的基础上实现了自己的 GreenThread,实际上就是 greenlet 类的扩展封装,而与 Greenlet 的不同是,eventlet 实现了自己调度器称为 Hub,Hub 类似于 Tornado 的 IOLoop,是单实例的。在 Hub 中有一个 event loop,根据不同的事件来切换到对应的 GreenThread。同时 Eventlet 还实现了一系列的补丁来使 Python 标准库中的 socket 等等 module 来支持 GreenThread 的切换。eventlet 的 Hub 可以被定制来实现自己调度过程。eventlet 目前支持 CPython 2.7 和 3.4+ 并将在未来删除,仅保留 CPython 3.5+ 支持。
- gevent:基于 libev 与 Greenlet 实现。不同于 eventlet 的用 python 实现 hub 调度,Gevent 通过 Cython 调用 libev 来实现一个高效的 event loop 调度循环。同时类似于 Event,Gevent 也有自己的 monkey_patch,在打了补丁后,完全可以使用 python 线程的方式来无感知的使用协程,减少了开发成本。gevent 官方文档:http://www.gevent.org/contents.html
二、关于 gevent
2.1、gevent的特点
- 基于 libev 的快速事件循环,Linux 上面的是 epoll 机制
- 基于 greenlet 的轻量级执行单元
- API 复用了 Python 标准库里的内容。API 的概念和 Python 标准库一致(如事件,队列)
- TCP/UDP/HTTP 服务器
- 支持 SSL 的协作式 sockets
- 子进程支持(通过 gevent.subprocess)
- 线程池
- greenlets 是确定性的。给定相同的绿色配置和相同的输入集,它们总是产生相同的输出
- gevent 每次遇到 io 操作,需要耗时等待时,会自动跳到下一个协程继续执行。
- gevent 的代码风格和线程非常相似,运行出来后的效果也非常相似。
- 通过 monkey patching 功能来使得第三方模块变成协作式
libevent 时一个事件分发引擎,greenlet 提供了轻量级线程的支持,gevent 就是基于这两个的一个专门处理网络逻辑的并行库。
- gevent.spawn 启动的所有协程,都是运行在同一个线程之中,所以协程不能跨线程同步数据。
- gevent.queue.Queue 是协程安全的。
- gevent 启动的并发协程,具体到 task function,不能有长时间阻塞的IO操作。因为 gevent 的协程特点是:当前协程阻塞了才会切换到别的协程。如果当前协程长时间阻塞,则不能显示(gevent.sleep(0),或隐式,由 gevent 来做)切换到别的协程。导致程序出问题。
- 如果有长时间阻塞的 IO 操作,还是用传统的线程模型比较好。
- 因为 gevent 的特点总结是:事件驱动 + 协程 + 非阻塞 IO,事件驱动指的是 libevnt 对 epool 的封装,是基于事件的方式处理 IO。协程指的是 greenlet,非阻塞 IO 指的是 gevent 已经 pathc 过的各种库,例如 socket 和 select 等等。
- 使用 gevent 的协程,最好要用 gevent 自身的非阻塞的库。如 httplib,socket,select 等等。
- gevent 适合处理大量无阻塞的任务,如果有实在不能把阻塞的部分变为非阻塞再交给 gevent 处理,就把阻塞的部分改为异步吧。
原理
原理:程序的重要部分是将任务函数封装到 gevent.spawn。
- 初始化的 greenlet 列表存在数组 threads 中,此数组被传给 gevent.joinall 函数。
- gevent.joinall 会阻塞当前流程,并执行所有给定的 greenlet , 执行流程只会在所有 greenlet 执行完后才会继续向下走。
gevent 实现了 python 标准库里面大部分的阻塞式系统调用,包括 socket、ssl、threading 和 select 等模块,而将这些阻塞式调用变为协作式运行。
2.2、猴子补丁(monkey patch)
- 猴子补丁的由来:猴子补丁的这个叫法起源于 Zope 框架,大家在修正 Zope 的 bug 时,经常在程序后面追加更新部分,这些被称作是 “杂牌军补丁(guerillapatch)”,后来 guerilla 就渐渐的写成了 gorllia(猩猩),再后来就写成了 monkey(猴子),所以猴子补丁的叫法是这么莫名其妙得来的。后来在动态语言中,不改变源代码而对功能进行追加和变更,统称为“猴子补丁”。 所以猴子补丁并不是 Python 中专有。猴子补丁这种东西充分利用了动态语言的灵活性,可以对现有的语言 API 进行追加,替换,修改Bug,甚至性能优化等等。使用猴子补丁的方式,gevent 能够修改标准库里面大部分的阻塞式系统调用,包括 socket、ssl、threading 和 select 等模块,而变为协作式运行。也就是通过猴子补丁的 monkey.patch_xxx() 来将 python 标准库中模块或函数 改成 gevent 中相应的具有协程的协作式对象。这样在不改变原有代码的情况下,将应用的阻塞式方法,变成协程式。
- **猴子补丁使用注意事项。**猴子补丁的功能很强大,但是也带来了很多的风险,尤其是像 gevnet 这种直接进行 API 替换的补丁,整个 Python 进程所使用的模块都会被替换,可能自己的代码能hold 住,但是其它第三方库,有时候问题并不好排查,即使排查出来也是很棘手,所以,就像松本建议的那样,如果要使用猴子补丁,那么只是做功能追加,尽量避免大规模的 API 覆盖。虽然猴子补丁仍然是邪恶的(evil),但在这种情况下它是“有用的邪恶(useful evil)”
2.3、Linux 的 epoll 和 libev
- 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 则不会。epoll 使用 mmap 加速内核与用户空间的消息传递。epoll 是通过内核与用户空间 mmap 同一块内存实现的。
- libev 机制:提供了指定文件描述符事件发生时调用回调函数机制。libev 是一个事件循环器:向 libev 注册感兴趣的事件,比如 socket 可读事件,libev 会对所注册的事件源进行管理,并在事件发生时触发相应的程序。
三、使用示例
1 | import gevent |
资料来源
https://blog.csdn.net/freeking101/article/details/53097420/
http://www.xuebuyuan.com/1604603.html
Gevent 指南(中文):http://xlambda.com/gevent-tutorial
Gevent 指南(中文)下载地址:http://download.csdn.net/download/freeking101/9924351
初试Gevent – 高性能的 Python 并发框架:http://python.jobbole.com/87041
Python 开发使用 Gevent:http://sdiehl.github.io/gevent-tutorial/