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实现网络端口转发和重定向的方法
Sep 19 Python
PyQt5每天必学之事件与信号
Apr 20 Python
Python爬虫设置代理IP(图文)
Dec 23 Python
python对绑定事件的鼠标、按键的判断实例
Jul 17 Python
python使用SQLAlchemy操作MySQL
Jan 02 Python
Pytorch使用MNIST数据集实现基础GAN和DCGAN详解
Jan 10 Python
Python数组拼接np.concatenate实现过程
Apr 18 Python
浅谈Python中的字符串
Jun 10 Python
Python pexpect模块及shell脚本except原理解析
Aug 03 Python
Python 将代码转换为可执行文件脱离python环境运行(步骤详解)
Jan 25 Python
python中sys模块的介绍与实例
Apr 17 Python
聊聊pytorch测试的时候为何要加上model.eval()
May 23 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
Yii学习总结之安装配置
2015/02/22 PHP
php实现多维数组排序的方法示例
2017/03/23 PHP
PHP实现的多维数组排序算法分析
2018/02/10 PHP
js onload事件不起作用示例分析
2013/10/09 Javascript
jquery 图片缩放拖动的简单实例
2014/01/08 Javascript
使用GruntJS构建Web程序之Tasks(任务)篇
2014/06/06 Javascript
基于Arcgis for javascript实现百度地图ABCD marker的效果
2015/09/12 Javascript
老生常谈JavaScript 正则表达式语法
2016/08/20 Javascript
javascript数组去重方法分析
2016/12/15 Javascript
AngularJS实现使用路由切换视图的方法
2017/01/24 Javascript
jQuery插件FusionCharts实现的2D面积图效果示例【附demo源码下载】
2017/03/06 Javascript
Vue2.0 事件的广播与接收(观察者模式)
2018/03/14 Javascript
JavaScript遍历数组的方法代码实例
2020/01/14 Javascript
JavaScript观察者模式原理与用法实例详解
2020/03/10 Javascript
解决vue init webpack 下载依赖卡住不动的问题
2020/11/09 Javascript
[00:52]黑暗之门更新 新英雄孽主驾临DOTA2
2016/08/24 DOTA
[01:16:37]【全国守擂赛】第三周决赛 Dark Knight vs. 一个弱队
2020/05/04 DOTA
[02:03]完美世界DOTA2联赛10月30日赛事集锦
2020/10/31 DOTA
[48:46]完美世界DOTA2联赛PWL S2 SZ vs FTD.C 第二场 11.19
2020/11/19 DOTA
跟老齐学Python之坑爹的字符编码
2014/09/28 Python
python保存字符串到文件的方法
2015/07/01 Python
Python实现PS滤镜功能之波浪特效示例
2018/01/26 Python
python+pyqt5实现KFC点餐收银系统
2019/01/24 Python
对Python Pexpect 模块的使用说明详解
2019/02/14 Python
详解python深浅拷贝区别
2019/06/24 Python
Django restframework 框架认证、权限、限流用法示例
2019/12/21 Python
python 实现保存最新的三份文件,其余的都删掉
2019/12/22 Python
使用 prometheus python 库编写自定义指标的方法(完整代码)
2020/06/29 Python
锐步英国官网:Reebok英国
2019/11/29 全球购物
求职简历自我评价范例
2014/03/12 职场文书
民生工程实施方案
2014/03/22 职场文书
《恐龙》教学反思
2014/04/27 职场文书
镇人大副主席民主生活会对照检查材料思想汇报
2014/10/01 职场文书
技术入股合作协议书
2014/10/07 职场文书
长辈生日祝福语大全(72句)
2019/08/09 职场文书
MySQL count(*)统计总数问题汇总
2022/09/23 MySQL