转载自https://www.cnblogs.com/zhaof/p/7536569.htmlhtml
event官网文档地址:http://www.gevent.org/contents.htmlpython
基本概念
咱们一般所说的协程Coroutine实际上是corporate routine的缩写,直接翻译为协同的例程,通常咱们都简称为协程。linux
在linux系统中,线程就是轻量级的进程,而咱们一般也把协程称为轻量级的线程即微线程。shell
进程和协程
下面对比一下进程和协程的相同点和不一样点:编程
相同点:
咱们均可以把他们看作是一种执行流,执行流能够挂起,而且后面能够在你挂起的地方恢复执行,这实际上均可以看作是continuation,关于这个咱们能够经过在linux上运行一个hello程序来理解:网络
shell进程和hello进程:并发
- 开始,shell进程在运行,等待命令行的输入
- 执行hello程序,shell经过系统调用来执行咱们的请求,这个时候系统调用会讲控制权传递给操做系统。操做系统保存shell进程的上下文,建立一个hello进程以及其上下文并将控制权给新的hello进程。
- hello进程终止后,操做系统恢复shell进程的上下文,并将控制权传回给shell进程
- shell进程继续等待下个命令的输入
当咱们挂起一个执行流的时,咱们要保存的东西:异步
- 栈, 其实在你切换前你的局部变量,以及要函数的调用都须要保存,不然都没法恢复
- 寄存器状态,这个其实用于当你的执行流恢复后要作什么
而寄存器和栈的结合就能够理解为上下文,上下文切换的理解:
CPU看上去像是在并发的执行多个进程,这是经过处理器在进程之间切换来实现的,操做系统实现这种交错执行的机制称为上下文切换socket
操做系统保持跟踪进程运行所需的全部状态信息。这种状态,就是上下文。
在任何一个时刻,操做系统都只能执行一个进程代码,当操做系统决定把控制权从当前进程转移到某个新进程时,就会进行上下文切换,即保存当前进程的上下文,恢复新进程的上下文,而后将控制权传递到新进程,新进程就会从它上次中止的地方开始。async
不一样点:
- 执行流的调度者不一样,进程是内核调度,而协程是在用户态调度,也就是说进程的上下文是在内核态保存恢复的,而协程是在用户态保存恢复的,很显然用户态的代价更低
- 进程会被强占,而协程不会,也就是说协程若是不主动让出CPU,那么其余的协程,就没有执行的机会。
- 对内存的占用不一样,实际上协程能够只须要4K的栈就足够了,而进程占用的内存要大的多
- 从操做系统的角度讲,多协程的程序是单进程,单协程
线程和协程
既然咱们上面也说了,协程也被称为微线程,下面对比一下协程和线程:
- 线程之间须要上下文切换成本相对协程来讲是比较高的,尤为在开启线程较多时,但协程的切换成本很是低。
- 一样的线程的切换更多的是靠操做系统来控制,而协程的执行由咱们本身控制
咱们经过下面的图更容易理解:
从上图能够看出,协程只是在单一的线程里不一样的协程之间切换,其实和线程很像,线程是在一个进程下,不一样的线程之间作切换,这也多是协程称为微线程的缘由吧
继续分析协程:
Gevent
Gevent是一种基于协程的Python网络库,它用到Greenlet提供的,封装了libevent事件循环的高层同步API。它让开发者在不改变编程习惯的同时,用同步的方式写异步I/O的代码。
使用Gevent的性能确实要比用传统的线程高,甚至高不少。但这里不得不说它的一个坑:
- Monkey-patching,咱们都叫猴子补丁,由于若是使用了这个补丁,Gevent直接修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协做式运行。可是咱们没法保证你在复杂的生产环境中有哪些地方使用这些标准库会因为打了补丁而出现奇怪的问题
- 第三方库支持。得确保项目中用到其余用到的网络库也必须使用纯Python或者明确说明支持Gevent
既然Gevent用的是Greenlet,咱们经过下图来理解greenlet:
每一个协程都有一个parent,最顶层的协程就是man thread或者是当前的线程,每一个协程遇到IO的时候就把控制权交给最顶层的协程,它会看那个协程的IO event已经完成,就将控制权给它。
下面是greenlet一个例子
1 from greenlet import greenlet 2 3 def test1(x,y): 4 z = gr2.switch(x+y) 5 print(z) 6 7 8 def test2(u): 9 print(u) 10 gr1.switch(42) 11 12 13 gr1 = greenlet(test1) 14 gr2 = greenlet(test2) 15 16 17 gr1.switch("hello",'world')
greenlet(run=None, parent=None): 建立一个greenlet实例.
gr.parent:每个协程都有一个父协程,当前协程结束后会回到父协程中执行,该 属性默认是建立该协程的协程.
gr.run: 该属性是协程实际运行的代码. run方法结束了,那么该协程也就结束了.
gr.switch(*args, **kwargs): 切换到gr协程.
gr.throw(): 切换到gr协程,接着抛出一个异常.
下面是gevent的一个例子:
1 import gevent 2 3 def func1(): 4 print("start func1") 5 gevent.sleep(1) 6 print("end func1") 7 8 9 def func2(): 10 print("start func2") 11 gevent.sleep(1) 12 print("end func2") 13 14 gevent.joinall( 15 [ 16 gevent.spawn(func1), 17 gevent.spawn(func2) 18 ] 19 )
关于gevent中队列的使用
gevent中也有本身的队列,可是有一个场景我用的过程当中发现一个问题,就是若是我在协程中经过这个q来传递数据,若是对了是空的时候,从队列获取数据的那个协程就会被切换到另一个协程中,这个协程用于往队列里put放入数据,问题就出在,gevent不认为这个放入数据为IO操做,并不会切换到上一个协程中,会把这个协程的任务完成后在切换到另一个协程。我本来想要实现的效果是往对了放入数据后就会切换到get的那个协程。(或许我这里理解有问题)下面是测试代码:
1 import gevent 2 from gevent.queue import Queue 3 4 5 def func(): 6 for i in range(10): 7 8 print("int the func") 9 q.put("test") 10 11 def func2(): 12 for i in range(10): 13 print("int the func2") 14 res = q.get() 15 print("--->",res) 16 17 q = Queue() 18 gevent.joinall( 19 [ 20 gevent.spawn(func2), 21 gevent.spawn(func), 22 ] 23 )
这段代码的运行效果为:
若是我在fun函数的q.put("test")后面添加gevent.sleep(0),就会是以下效果:
本来我预测的在不修改代码的状况下就应该是第二个图的结果,可是实际倒是第一个图的结果(这个问题多是我本身没研究明白,后面继续研究)
关于Gevent的问题
就像我上面说的gevent和第三方库配合使用会有一些问题,能够总结为:
python协程的库能够直接monkey path
C写成的库能够采用豆瓣开源的greenify来打patch(这个功能本身准备后面作测试)
不过总的来讲gevent目前为止仍是有不少缺陷,而且不是官网标准库,而在python3中有一个官网正在作而且在3.6中已经稳定的库asyncio,这也是一个很是具备野心的库,很是建议学习,我也准备后面深刻了解