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 相关文章推荐
pyqt4教程之实现半透明的天气预报界面示例
Mar 02 Python
python创建线程示例
May 06 Python
举例讲解Python中装饰器的用法
Apr 27 Python
Python中创建字典的几种方法总结(推荐)
Apr 27 Python
Python matplotlib绘图可视化知识点整理(小结)
Mar 16 Python
Pandas中把dataframe转成array的方法
Apr 13 Python
python3基于OpenCV实现证件照背景替换
Jul 18 Python
Python异常处理操作实例详解
Aug 28 Python
python列表,字典,元组简单用法示例
Jul 11 Python
Django框架教程之中间件MiddleWare浅析
Dec 29 Python
Tensorflow设置显存自适应,显存比例的操作
Feb 03 Python
python numpy--数组的组合和分割实例
Feb 24 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
如何在symfony中导出为CSV文件中的数据
2011/10/06 PHP
PHP字典树(Trie树)定义与实现方法示例
2017/10/09 PHP
Yii框架数据库查询、增加、删除操作示例
2019/10/14 PHP
JavaScript 核心参考教程 内置对象
2009/10/13 Javascript
Eval and new funciton not the same thing
2012/12/27 Javascript
jquery.Callbacks的实现详解
2016/11/30 Javascript
Bootstrap基本组件学习笔记之列表组(11)
2016/12/07 Javascript
bootstrap日历插件datetimepicker使用方法
2016/12/14 Javascript
JS实现的点击表头排序功能示例
2017/03/27 Javascript
JavaScript实现滑动导航栏效果
2017/08/30 Javascript
JavaScript生成指定范围的时间列表
2018/03/19 Javascript
react中使用swiper的具体方法
2018/05/15 Javascript
解决vue-cli项目webpack打包后iconfont文件路径的问题
2018/09/01 Javascript
Egg.js 中 AJax 上传文件获取参数的方法
2018/10/10 Javascript
JavaScript实现选项卡效果的分析及步骤
2019/04/16 Javascript
微信公众号开发之微信支付代码记录的实现
2019/10/16 Javascript
Django权限机制实现代码详解
2018/02/05 Python
Python3网络爬虫开发实战之极验滑动验证码的识别
2019/08/02 Python
wxPython电子表格功能wx.grid实例教程
2019/11/19 Python
详解python 条件语句和while循环的实例代码
2020/12/28 Python
CSS3 mask 遮罩的具体使用方法
2017/11/03 HTML / CSS
Html5上传图片 移动端、PC端通用代码
2016/06/08 HTML / CSS
Canvas 文本转粒子效果的实现代码
2019/02/14 HTML / CSS
澳大利亚领先的在线药房:Pharmacy Online(有中文站)
2020/02/22 全球购物
J2EE相关知识面试题
2013/08/26 面试题
2014三八妇女节活动总结范文四篇
2014/03/09 职场文书
开学典礼主持词
2014/03/19 职场文书
分层教学实施方案
2014/03/19 职场文书
初中优秀班集体申报材料
2014/05/01 职场文书
倡议书的写法
2014/08/30 职场文书
2014年小学安全工作总结
2014/12/04 职场文书
党员发展大会主持词
2015/07/03 职场文书
2019终止劳动合同协议书最新范本!
2019/07/09 职场文书
Python使用sql语句对mysql数据库多条件模糊查询的思路详解
2021/04/12 Python
Mysql如何查看是否使用到索引
2022/12/24 MySQL
Mysql的Table doesn't exist问题及解决
2022/12/24 MySQL