并发编程协程之Gevent

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

基本概念

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

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

进程和协程

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

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

shell进程和hello进程:

  1. 开始,shell进程在运行,等待命令行的输入
  2. 执行hello程序,shell通过系统调用来执行我们的请求,这个时候系统调用会讲控制权传递给操作系统。操作系统保存shell进程的上下文,创建一个hello进程以及其上下文并将控制权给新的hello进程。
  3. hello进程终止后,操作系统恢复shell进程的上下文,并将控制权传回给shell进程
  4. shell进程继续等待下个命令的输入

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

  1. 栈, 其实在你切换前你的局部变量,以及要函数的调用都需要保存,否则都无法恢复
  2. 寄存器状态,这个其实用于当你的执行流恢复后要做什么

而寄存器和栈的结合就可以理解为上下文,上下文切换的理解:
CPU看上去像是在并发的执行多个进程,这是通过处理器在进程之间切换来实现的,操作系统实现这种交错执行的机制称为上下文切换

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

不同点:

  1. 执行流的调度者不同,进程是内核调度,而协程是在用户态调度,也就是说进程的上下文是在内核态保存恢复的,而协程是在用户态保存恢复的,很显然用户态的代价更低
  2. 进程会被强占,而协程不会,也就是说协程如果不主动让出CPU,那么其他的协程,就没有执行的机会。
  3. 对内存的占用不同,实际上协程可以只需要4K的栈就足够了,而进程占用的内存要大的多
  4. 从操作系统的角度讲,多协程的程序是单进程,单协程

线程和协程

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

  1. 线程之间需要上下文切换成本相对协程来说是比较高的,尤其在开启线程较多时,但协程的切换成本非常低。
  2. 同样的线程的切换更多的是靠操作系统来控制,而协程的执行由我们自己控制

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

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

继续分析协程:

Gevent

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

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

  1. Monkey-patching,我们都叫猴子补丁,因为如果使用了这个补丁,Gevent直接修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协作式运行。但是我们无法保证你在复杂的生产环境中有哪些地方使用这些标准库会由于打了补丁而出现奇怪的问题
  2. 第三方库支持。得确保项目中用到其他用到的网络库也必须使用纯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. def test2(u):
  8. print(u)
  9. gr1.switch(42)
  10.  
  11. gr1 = greenlet(test1)
  12. gr2 = greenlet(test2)
  13.  
  14. 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. def func2():
  9. print("start func2")
  10. gevent.sleep(1)
  11. print("end func2")
  12.  
  13. gevent.joinall(
  14. [
  15. gevent.spawn(func1),
  16. gevent.spawn(func2)
  17. ]
  18. )

关于gevent中队列的使用

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

  1. import gevent
  2. from gevent.queue import Queue
  3.  
  4. def func():
  5. for i in range(10):
  6.  
  7. print("int the func")
  8. q.put("test")
  9.  
  10. def func2():
  11. for i in range(10):
  12. print("int the func2")
  13. res = q.get()
  14. print("--->",res)
  15.  
  16. q = Queue()
  17. gevent.joinall(
  18. [
  19. gevent.spawn(func2),
  20. gevent.spawn(func),
  21. ]
  22. )

这段代码的运行效果为:

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

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

关于Gevent的问题

就像我上面说的gevent和第三方库配合使用会有一些问题,可以总结为:
  python协程的库可以直接monkey path 
  C写成的库可以采用豆瓣开源的greenify来打patch

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

 

并发编程协程(Coroutine)之Gevent的更多相关文章

  1. Python并发编程协程(Coroutine)之Gevent

    Gevent官网文档地址:http://www.gevent.org/contents.html 基本概念 我们通常所说的协程Coroutine其实是corporate routine的缩写,直接翻译 ...

  2. 14 并发编程-(协程)-greenlet模块&gevent模块

    1.实现多个任务之间进行切换,yield.greenlet都没有实现检测I/O,greenlet在实现多任务切换下更简单 from greenlet import greenlet def eat(n ...

  3. 并发编程~~~协程~~~greenlet模块, gevent模块

    一 协程 1. 协程: 单线程下的并发,又称微线程,纤程.协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的. 并发真正的核心: 切换并且保持状态. 开启协程并发的执行,自己的程序把控着C ...

  4. python 并发编程 协程 目录

    python 并发编程 协程 协程介绍 python 并发编程 协程 greenlet模块 python 并发编程 协程 gevent模块 python 并发编程 基于gevent模块实现并发的套接字 ...

  5. 并发编程 - 协程 - 1.协程概念/2.greenlet模块/3.gevent模块/4.gevent实现并发的套接字通信

    1.协程并发:切+保存状态单线程下实现并发:协程 切+ 保存状态 yield 遇到io切,提高效率 遇到计算切,并没有提高效率 检测单线程下 IO行为 io阻塞 切 相当于骗操作系统 一直处于计算协程 ...

  6. python语法基础-并发编程-协程-长期维护

    ###############    协程    ############## # 协程 # 小知识点, # 协程和进程和线程一样都是实现并发的手段, # 开启一个线程,创建一个线程,还是需要开销, ...

  7. python 并发编程 协程池

    协程池 from gevent.pool import Pool from gevent import monkey;monkey.patch_all() import gevent from gev ...

  8. python 并发编程 协程 gevent模块

    一 gevent模块 gevent应用场景: 单线程下,多个任务,io密集型程序 安装 pip3 install gevent Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步 ...

  9. Python之并发编程-协程

    目录 一.介绍 二. yield.greenlet.gevent介绍 1.yield 2.greenlet 3.gevent 一.介绍 协程:是单线程下的并发,又称微线程,纤程.英文名Coroutin ...

随机推荐

  1. PAT 天梯赛 L1-049. 天梯赛座位分配 【循环】

    题目链接 https://www.patest.cn/contests/gplt/L1-049 思路 用一个二维数组来保存一个学校每个队员的座位号 然后需要判断一下 目前的座位号 与该学校当前状态下最 ...

  2. ArcGIS服务器的feature图层限制

    今天遇到了esri.layers.FeatureLayer发布一个宗地图层,里面有些数据未显示,导致数据显示不全,原来是服务中数据返回参数限制. ArcGIS的feature图层(在JavaScrip ...

  3. POJ2492 A Bug's Life —— 种类并查集

    题目链接:http://poj.org/problem?id=2492 A Bug's Life Time Limit: 10000MS   Memory Limit: 65536K Total Su ...

  4. spring mvc提交日期类型参数

    如题,spring mvc直接提交Date类型参数会报错,400 bad request的错误.在controller里加上 @InitBinder protected void initBinder ...

  5. CNN卷积神经网络_深度残差网络 ResNet——解决神经网络过深反而引起误差增加的根本问题,Highway NetWork 则允许保留一定比例的原始输入 x。(这种思想在inception模型也有,例如卷积是concat并行,而不是串行)这样前面一层的信息,有一定比例可以不经过矩阵乘法和非线性变换,直接传输到下一层,仿佛一条信息高速公路,因此得名Highway Network

    from:https://blog.csdn.net/diamonjoy_zone/article/details/70904212 环境:Win8.1 TensorFlow1.0.1 软件:Anac ...

  6. adb pull / push

    刚才搞了半天想pull,就是pull不成,如图: 看出哪里有问题了吗? 问题就是我不该在shell里面运行adb pull! 正确的做法: 在任意一处打开命令行比如图中的桌面, adb pull /s ...

  7. liunx环境下安装tomcat

    第一:下载tomcat的安装包

  8. 使用PowerDesigner设计数据库

    1.快捷键CTRL+N 创建 New Model 选择如下图,并设置 Model name 单击OK 2.使用工具添加实体 双击Entity_1,填上如下图信息   切换选项卡,添加属性信息 其中 M ...

  9. Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

    从浏览器中创建 XMLHttpRequests 从 node.js 创建 http 请求 支持 Promise API 拦截请求和响应 转换请求数据和响应数据 取消请求 自动转换 JSON 数据 客户 ...

  10. HDU3065(AC自动机入门题)

    病毒侵袭持续中 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Sub ...