Gevent官网文档地址: http://www.gevent.org/contents.html
进程、线程、协程区分
我们通常所说的协程Coroutine其实是corporate routine的缩写,直接翻译为协同的例程,一般我们都简称为协程。
在linux系统中,线程就是轻量级的进程,而我们通常也把协程称为轻量级的线程即微线程。
进程和协程
下面对比一下进程和协程的相同点和不同点:
相同点:
- 相同点存在于,当我们挂起一个执行流的时,我们要保存的东西:
- 栈, 其实在你切换前你的局部变量,以及要函数的调用都需要保存,否则都无法恢复
寄存器状态,这个其实用于当你的执行流恢复后要做什么
不同点:
- 执行流的调度者不同,进程是内核调度,而协程是在用户态调度,也就是说进程的上下文是在内核态保存恢复的,而协程是在用户态保存恢复的,很显然用户态的代价更低
- 进程会被强占,而协程不会,也就是说协程如果不主动让出CPU,那么其他的协程,就没有执行的机会。
- 对内存的占用不同,实际上协程可以只需要4K的栈就足够了,而进程占用的内存要大的多
- 从操作系统的角度讲,多协程的程序是单进程,单协程
线程和协程
既然我们上面也说了,协程也被称为微线程,下面对比一下协程和线程:
- 线程之间需要上下文切换成本相对协程来说是比较高的,尤其在开启线程较多时,但协程的切换成本非常低。
- 同样的线程的切换更多的是靠操作系统来控制,而协程的执行由我们自己控制。
协程只是在单一的线程里不同的协程之间切换,其实和线程很像,线程是在一个进程下,不同的线程之间做切换,这也可能是协程称为微线程的原因吧。
Gevent模块
Gevent是一种基于协程的Python网络库,它用到Greenlet提供的,封装了libevent事件循环的高层同步API。它让开发者在不改变编程习惯的同时,用同步的方式写异步I/O的代码。
简单示例:
结果:
猴子补丁 Monkey patching
上述代码的第一行就是对socket标准库打上猴子补丁,此后socket标准库中的类和方法都会被替换成非阻塞式的,所有其他的代码都不用修改,这样协程的效率就真正体现出来了。Python中其它标准库也存在阻塞的情况,gevent提供了”monkey.patch_all()”方法将所有标准库都替换。
获取协程状态
- started属性/ready()方法:判断协程是否已启动。
- successful()方法:判断协程是否成功运行且没有抛出异常。
- value属性:获取协程执行完之后的返回值。
另外,greenlet协程运行过程中发生的异常是不会被抛出到协程外的,因此需要用协程对象的”exception”属性来获取协程中的异常。
下面的例子很好的演示了各种方法和属性的使用。
协程运行超时控制
之前我们讲过在”gevent.joinall()”方法中可以传入timeout参数来设置超时,我们也可以在全局范围内设置超时时间:
上例中,我们将超时设为2秒,此后所有协程的运行,如果超过两秒就会抛出”Timeout”异常。我们也可以将超时设置在with语句内,这样该设置只在with语句块中有效:
此外,我们可以指定超时所抛出的异常,来替换默认的”Timeout”异常。比如下例中超时就会抛出我们自定义的”TooLong”异常。
协程间通信
事件(Event)对象
greenlet协程间的异步通讯可以使用事件(Event)对象。该对象的”wait()”方法可以阻塞当前协程,而”set()”方法可以唤醒之前阻塞的协程。在下面的例子中,5个waiter协程都会等待事件evt,当setter协程在3秒后设置evt事件,所有的waiter协程即被唤醒。
AsyncResult事件
除了Event事件外,gevent还提供了AsyncResult事件,它可以在唤醒时传递消息。让我们将上例中的setter和waiter作如下改动:
队列 Queue
队列Queue的概念相信大家都知道,我们可以用它的put和get方法来存取队列中的元素。gevent的队列对象可以让greenlet协程之间安全的访问。运行下面的程序,你会看到3个消费者会分别消费队列中的产品,且消费过的产品不会被另一个消费者再取到:
注意:协程队列跟线程队列是一样的,put和get方法都是阻塞式的,它们都有非阻塞的版本:put_nowait和get_nowait。如果调用get方法时队列为空,则是不会抛出”gevent.queue.Empty”异常。我们只能使用get_nowait()的方式让气抛出异常。
信号量
信号量可以用来限制协程并发的个数。它有两个方法,acquire和release。顾名思义,acquire就是获取信号量,而release就是释放。当所有信号量都已被获取,那剩余的协程就只能等待任一协程释放信号量后才能得以运行:
上面的例子中,我们初始化了”BoundedSemaphore”信号量,并将其个数定为2。所以同一个时间,只能有两个worker协程被调度。程序运行后的结果如下:
如果信号量个数为1,那就等同于同步锁。
协程本地变量
同线程类似,协程也有本地变量,也就是只在当前协程内可被访问的变量:
通过将变量存放在local对象中,即可将其的作用域限制在当前协程内,当其他协程要访问该变量时,就会抛出异常。不同协程间可以有重名的本地变量,而且互相不影响。因为协程本地变量的实现,就是将其存放在以的”greenlet.getcurrent()”的返回为键值的私有的命名空间内。
多并发socket模型
服务器端:
当客户端连接上服务器端时,服务器端通过开辟一个协程与该客户端完成交互任务,同时由于使用了Gevent协程的方式,在每个客户端与服务器交互时,并不会影响到服务器端的工作。
客户端:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/7552.html