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基于multiprocessing的多进程创建方法
Jun 04 Python
Python之dict(或对象)与json之间的互相转化实例
Jun 05 Python
python format 格式化输出方法
Jul 16 Python
pycharm中使用anaconda部署python环境的方法步骤
Dec 19 Python
详解10个可以快速用Python进行数据分析的小技巧
Jun 24 Python
Python 使用元类type创建类对象常见应用详解
Oct 17 Python
在django admin详情表单显示中添加自定义控件的实现
Mar 11 Python
python def 定义函数,调用函数方式
Jun 02 Python
python基于exchange函数发送邮件过程详解
Nov 06 Python
教你怎么用Python处理excel实现自动化办公
Apr 30 Python
Python中time与datetime模块使用方法详解
Mar 31 Python
解决IDEA翻译插件Translation报错更新TTK失败不能使用
Apr 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
判断Keep-Alive模式的HTTP请求的结束的实现代码
2011/08/06 PHP
PHP中3种生成XML文件方法的速度效率比较
2012/10/06 PHP
PHP实现的简单三角形、矩形周长面积计算器分享
2014/11/18 PHP
php面向对象与面向过程两种方法给图片添加文字水印
2015/08/26 PHP
添加到收藏夹代码(兼容几乎所有的浏览器)
2007/01/09 Javascript
犀利的js 函数集合
2009/06/11 Javascript
有道JavaScript监听浏览器的问题
2010/06/23 Javascript
jquery实现隐藏与显示动画效果/输入框字符动态递减/导航按钮切换
2013/07/01 Javascript
在页面加载完成后通过jquery给多个span赋值
2014/05/21 Javascript
javascript快速排序算法详解
2014/09/17 Javascript
使用postMesssage()实现跨域iframe页面间的信息传递方法
2016/03/29 Javascript
JS中this上下文对象使用方式
2016/10/09 Javascript
JSON对象转化为字符串详解
2017/08/11 Javascript
[16:04]DOTA2海涛带你玩炸弹 9月5日更新内容详解
2014/09/05 DOTA
python3写爬取B站视频弹幕功能
2017/12/22 Python
Python读取MRI并显示为灰度图像实例代码
2018/01/03 Python
使用python实现链表操作
2018/01/26 Python
Python使用functools实现注解同步方法
2018/02/06 Python
Python中xml和json格式相互转换操作示例
2018/12/05 Python
简单了解python中对象的取反运算符
2019/07/01 Python
python3多线程知识点总结
2019/09/26 Python
python运用pygame库实现双人弹球小游戏
2019/11/25 Python
Python list运算操作代码实例解析
2020/01/20 Python
python dict乱码如何解决
2020/06/07 Python
Python grequests模块使用场景及代码实例
2020/08/10 Python
Gucci法国官方网站:意大利奢侈品牌
2018/07/25 全球购物
茱莉蔻美国官网:Jurlique美国
2020/11/24 全球购物
Java中有几种方法可以实现一个线程?用什么关键字修饰同步方法?stop()和suspend()方法为何不推荐使用?
2015/08/04 面试题
仓库理货员岗位职责
2013/12/18 职场文书
2014银行领导班子群众路线对照检查材料思想汇报
2014/09/17 职场文书
2014旅游局领导班子四风问题对照检查材料思想汇报
2014/09/19 职场文书
教师对照四风自我剖析材料
2014/09/30 职场文书
个人先进事迹材料
2014/12/29 职场文书
nginx基于域名,端口,不同IP的虚拟主机设置的实现
2021/03/31 Servers
Python实现归一化算法详情
2022/03/18 Python
使用 Docker Compose 构建复杂的多容器App
2022/04/30 Servers