Python并发编程协程(Coroutine)之Gevent详解


Posted in Python onDecember 27, 2017

Gevent官网文档地址:http://www.gevent.org/contents.html

基本概念

我们通常所说的协程Coroutine其实是corporateroutine的缩写,直接翻译为协同的例程,一般我们都简称为协程。

在linux系统中,线程就是轻量级的进程,而我们通常也把协程称为轻量级的线程即微线程。

进程和协程

下面对比一下进程和协程的相同点和不同点:

相同点:

我们都可以把他们看做是一种执行流,执行流可以挂起,并且后面可以在你挂起的地方恢复执行,这实际上都可以看做是continuation,关于这个我们可以通过在linux上运行一个hello程序来理解:

Python并发编程协程(Coroutine)之Gevent详解

shell进程和hello进程:

开始,shell进程在运行,等待命令行的输入

执行hello程序,shell通过系统调用来执行我们的请求,这个时候系统调用会讲控制权传递给操作系统。操作系统保存shell进程的上下文,创建一个hello进程以及其上下文并将控制权给新的hello进程。

hello进程终止后,操作系统恢复shell进程的上下文,并将控制权传回给shell进程

shell进程继续等待下个命令的输入

当我们挂起一个执行流的时,我们要保存的东西:

栈,其实在你切换前你的局部变量,以及要函数的调用都需要保存,否则都无法恢复

寄存器状态,这个其实用于当你的执行流恢复后要做什么

而寄存器和栈的结合就可以理解为上下文,上下文切换的理解:

CPU看上去像是在并发的执行多个进程,这是通过处理器在进程之间切换来实现的,操作系统实现这种交错执行的机制称为上下文切换

操作系统保持跟踪进程运行所需的所有状态信息。这种状态,就是上下文。

在任何一个时刻,操作系统都只能执行一个进程代码,当操作系统决定把控制权从当前进程转移到某个新进程时,就会进行上下文切换,即保存当前进程的上下文,恢复新进程的上下文,然后将控制权传递到新进程,新进程就会从它上次停止的地方开始。

不同点:

执行流的调度者不同,进程是内核调度,而协程是在用户态调度,也就是说进程的上下文是在内核态保存恢复的,而协程是在用户态保存恢复的,很显然用户态的代价更低

进程会被强占,而协程不会,也就是说协程如果不主动让出CPU,那么其他的协程,就没有执行的机会。

对内存的占用不同,实际上协程可以只需要4K的栈就足够了,而进程占用的内存要大的多

从操作系统的角度讲,多协程的程序是单进程,单协程

线程和协程

既然我们上面也说了,协程也被称为微线程,下面对比一下协程和线程:

线程之间需要上下文切换成本相对协程来说是比较高的,尤其在开启线程较多时,但协程的切换成本非常低。

同样的线程的切换更多的是靠操作系统来控制,而协程的执行由我们自己控制

我们通过下面的图更容易理解:

Python并发编程协程(Coroutine)之Gevent详解

Python并发编程协程(Coroutine)之Gevent详解

从上图可以看出,协程只是在单一的线程里不同的协程之间切换,其实和线程很像,线程是在一个进程下,不同的线程之间做切换,这也可能是协程称为微线程的原因吧

继续分析协程:

Python并发编程协程(Coroutine)之Gevent详解

Gevent

Gevent是一种基于协程的Python网络库,它用到Greenlet提供的,封装了libevent事件循环的高层同步API。它让开发者在不改变编程习惯的同时,用同步的方式写异步I/O的代码。

使用Gevent的性能确实要比用传统的线程高,甚至高很多。但这里不得不说它的一个坑:

Monkey-patching,我们都叫猴子补丁,因为如果使用了这个补丁,Gevent直接修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和select等模块,而变为协作式运行。但是我们无法保证你在复杂的生产环境中有哪些地方使用这些标准库会由于打了补丁而出现奇怪的问题

第三方库支持。得确保项目中用到其他用到的网络库也必须使用纯Python或者明确说明支持Gevent

既然Gevent用的是Greenlet,我们通过下图来理解greenlet:

Python并发编程协程(Coroutine)之Gevent详解

每个协程都有一个parent,最顶层的协程就是man thread或者是当前的线程,每个协程遇到IO的时候就把控制权交给最顶层的协程,它会看那个协程的IO event已经完成,就将控制权给它。

下面是greenlet一个例子

from greenlet import greenlet

def test1(x,y):
  z = gr2.switch(x+y)
  print(z)

def test2(u):
  print(u)
  gr1.switch(42)

gr1 = greenlet(test1)
gr2 = greenlet(test2)

gr1.switch("hello",'world')

greenlet(run=None, parent=None): 创建一个greenlet实例.
gr.parent:每一个协程都有一个父协程,当前协程结束后会回到父协程中执行,该 属性默认是创建该协程的协程.
gr.run: 该属性是协程实际运行的代码. run方法结束了,那么该协程也就结束了.
gr.switch(*args, **kwargs): 切换到gr协程.
gr.throw(): 切换到gr协程,接着抛出一个异常.

下面是gevent的一个例子:

import gevent
def func1():
  print("start func1")
  gevent.sleep(1)
  print("end func1")
def func2():
  print("start func2")
  gevent.sleep(1)
  print("end func2")

gevent.joinall(
  [
    gevent.spawn(func1),
    gevent.spawn(func2)
  ]
)

关于gevent中队列的使用

gevent中也有自己的队列,但是有一个场景我用的过程中发现一个问题,就是如果我在协程中通过这个q来传递数据,如果对了是空的时候,从队列获取数据的那个协程就会被切换到另外一个协程中,这个协程用于往队列里put放入数据,问题就出在,gevent不认为这个放入数据为IO操作,并不会切换到上一个协程中,会把这个协程的任务完成后在切换到另外一个协程。我原本想要实现的效果是往对了放入数据后就会切换到get的那个协程。(或许我这里理解有问题)下面是测试代码:

import gevent
from gevent.queue import Queue
def func():
  for i in range(10):

    print("int the func")
    q.put("test")
def func2():
  for i in range(10):
    print("int the func2")
    res = q.get()
    print("--->",res)
q = Queue()
gevent.joinall(
  [
    gevent.spawn(func2),
    gevent.spawn(func),
  ]
)

这段代码的运行效果为:

Python并发编程协程(Coroutine)之Gevent详解

如果我在fun函数的q.put("test")后面添加gevent.sleep(0),就会是如下效果:

Python并发编程协程(Coroutine)之Gevent详解

原本我预测的在不修改代码的情况下就应该是第二个图的结果,但是实际却是第一个图的结果(这个问题可能是我自己没研究明白,后面继续研究)

关于Gevent的问题

就像我上面说的gevent和第三方库配合使用会有一些问题,可以总结为:
python协程的库可以直接monkey path
C写成的库可以采用豆瓣开源的greenify来打patch(这个功能自己准备后面做测试)

不过总的来说gevent目前为止还是有很多缺陷,并且不是官网标准库,而在python3中有一个官网正在做并且在3.6中已经稳定的库asyncio,这也是一个非常具有野心的库,非常建议学习,我也准备后面深入了解

总结

以上就是本文关于Python并发编程协程(Coroutine)之Gevent详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

Python 相关文章推荐
python利用hook技术破解https的实例代码
Mar 25 Python
Python判断操作系统类型代码分享
Nov 22 Python
用于统计项目中代码总行数的Python脚本分享
Apr 21 Python
Python遍历目录中的所有文件的方法
Jul 08 Python
python+matplotlib绘制3D条形图实例代码
Jan 17 Python
对pyqt5之menu和action的使用详解
Jun 20 Python
Python文本处理简单易懂方法解析
Dec 19 Python
Python模块的定义,模块的导入,__name__用法实例分析
Jan 07 Python
Python netmiko模块的使用
Feb 14 Python
基于opencv的selenium滑动验证码的实现
Jul 24 Python
Python colormap库的安装和使用详情
Oct 06 Python
Pytest测试框架基本使用方法详解
Nov 25 Python
利用 python 对目录下的文件进行过滤删除
Dec 27 #Python
python中使用%与.format格式化文本方法解析
Dec 27 #Python
python类的方法属性与方法属性的动态绑定代码详解
Dec 27 #Python
python中的迭代和可迭代对象代码示例
Dec 27 #Python
python并发编程之线程实例解析
Dec 27 #Python
Python实现连接postgresql数据库的方法分析
Dec 27 #Python
Python机器学习之SVM支持向量机
Dec 27 #Python
You might like
php中$_SERVER[PHP_SELF] 和 $_SERVER[SCRIPT_NAME]之间的区别
2009/09/05 PHP
一个PHP分页类的代码
2011/05/18 PHP
通过dbi使用perl连接mysql数据库的方法
2014/04/16 PHP
在PHP中使用FastCGI解析漏洞及修复方案
2015/11/10 PHP
Javascript实例教程(19) 使用HoTMetal(3)
2006/12/23 Javascript
用js做一个小游戏平台 (一)
2009/12/29 Javascript
js 编码转换 gb2312 和 utf8 互转的2种方法
2013/08/07 Javascript
JavaScript面向对象编程入门教程
2014/04/16 Javascript
jQuery检测返回值的数据类型
2015/07/13 Javascript
浅谈node.js中async异步编程
2015/10/22 Javascript
实例讲解js验证表单项是否为空的方法
2016/01/09 Javascript
jQuery筛选数组之grep、each、inArray、map的用法及遍历json对象
2016/06/20 Javascript
jQuery实现的自动加载页面功能示例
2016/09/04 Javascript
详解Angular的内置过滤器和自定义过滤器【推荐】
2016/12/26 Javascript
Javascript实现信息滚动效果
2017/05/18 Javascript
vue-router 导航钩子的具体使用方法
2017/08/31 Javascript
vue.js分页中单击页码更换页面内容的方法(配合spring springmvc)
2018/02/10 Javascript
js实现各浏览器全屏代码实例
2018/07/03 Javascript
JS 中可以提升幸福度的小技巧(可以识别更多另类写法)
2018/07/28 Javascript
基于vue通用表单解决方案的思考与分析
2019/03/16 Javascript
详解从0开始搭建微信小程序(前后端)的全过程
2019/04/15 Javascript
在vue中使用Base64转码的案例
2020/08/07 Javascript
js 图片懒加载的实现
2020/10/21 Javascript
[22:20]初生之犊-TI4第5名LGD战队纪录片
2014/08/13 DOTA
python实现字符串连接的三种方法及其效率、适用场景详解
2017/01/13 Python
Django Admin 实现外键过滤的方法
2017/09/29 Python
深入理解python中函数传递参数是值传递还是引用传递
2017/11/07 Python
python numpy 按行归一化的实例
2019/01/21 Python
pandas实现将dataframe满足某一条件的值选出
2019/06/12 Python
Python rabbitMQ如何实现生产消费者模式
2020/08/24 Python
friso美素佳儿官方海外旗舰店:荷兰原产原罐
2017/07/03 全球购物
自我评价范文点评
2013/12/04 职场文书
学期研究性学习个人的自我评价
2014/01/09 职场文书
蟋蟀的住宅教学反思
2014/04/26 职场文书
JavaScript实现简单计时器
2021/06/22 Javascript
redis数据结构之压缩列表
2022/03/21 Redis