gevent 中最主要的是 greenlet,greenlet 是 Python 的 C 扩展,用来实现协程。
协程 [[Coroutine]],就是可以暂时中断,之后再继续执行的程序
事实上 Python 就有最基础的 Coroutine,也就是生成器 generator
协程就是一种特殊的并发机制,其调度”就是指什么时候调用什么函数”完全由程序员指定
- 进程是一个操作系统级别的概念,拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
- 线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程)。
- 协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
greenlet
看一个最经典的生产者消费者模型。
from greenlet import greenlet
from time import sleep
def consumer():
last= ''
while True:
receival = pro.switch(last)
if receival is not None:
print(f'Consume {receival}')
last = receival
sleep(1)
def producer(n):
con.switch()
x = 0
while x < n:
x += 1
print(f'Produce {x}')
last = con.switch(x)
pro = greenlet(producer)
con = greenlet(consumer)
pro.switch(10)
gevent
gevent 是一个并发网络库,他的协程是基于 greenlet 的。并基于 libev 实现快速事件循环(Linux 上是 epoll,FreeBSD 上是 kqueue,Mac OS X 上是 select)。
一个比较通俗的解释就是当 greenlet 遇到 IO 操作,比如访问网络时自动切换到其他 greenlet ,等 IO 操作完成,在适当的时候切换回来继续执行。由于 IO 操作非常耗时,经常使程序处于等待状态,所以 gevent 保证总是有 greenlet 在运行,而不是等待 IO。
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),
])
gevent.spawn()
方法会创建一个新的 greenlet 协程对象,gevent.joinall()
方法会等待所有传入的 greenlet 协程运行结束后再退出。
优缺点
gevent 的优点如下:
- 执行效率高,子程序切换几乎没有开销,与多线程相比,线程越多,协程性能越明显
- 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁
- I/O 多路复用是在一个进程内部处理多个逻辑流程,不用进行进程切换,性能较高,另外流程间共享信息简单。
- 协程有编程语言提供,由程序员控制进行切换,所以没有线程安全问题,可以用来处理状态机,并发请求等 IO 密集型任务
gevent 缺点如下:
- 不能利用 CPU 多核优势
- 程序流程被事件处理切割成一个个小块,程序比较复杂,难于理解
所以,协程的适用场景,应该是一些IO 密集型的并行程序,而对应的计算密集型,应当采用传统的多线程、多进程方案。
相关知识点
- Threads
- Processes
- SubProcess (Or os.system calls)
- concurrent.futures
- gevent, greenlet etc
- asyncio aiodns
- cython (Disabling the GIL)
- Writing C extensions