Python greenlet实现原理和使用示例


Posted in Python onSeptember 24, 2014

最近开始研究Python的并行开发技术,包括多线程,多进程,协程等。逐步整理了网上的一些资料,今天整理了一下greenlet相关的资料。

并发处理的技术背景

并行化处理目前很受重视, 因为在很多时候,并行计算能大大的提高系统吞吐量,尤其在现在多核多处理器的时代, 所以像lisp这种古老的语言又被人们重新拿了起来, 函数式编程也越来越流行。 介绍一个python的并行处理的一个库: greenlet。 python 有一个非常有名的库叫做 stackless ,用来做并发处理, 主要是弄了个叫做tasklet的微线程的东西, 而greenlet 跟stackless的最大区别是, 他很轻量级?不够, 最大的区别是greenlet需要你自己来处理线程切换, 就是说,你需要自己指定现在执行哪个greenlet再执行哪个greenlet。

greenlet的实现机制

以前使用python开发web程序,一直使用的是fastcgi模式.然后每个进程中启动多个线程来进行请求处理.这里有一个问题就是需要保证每个请求响应时间都要特别短,不然只要多请求几次慢的就会让服务器拒绝服务,因为没有线程能够响应请求了.平时我们的服务上线都会进行性能测试的,所以正常情况没有太大问题.但是不可能所有场景都测试到.一旦出现就会让用户等好久没有响应.部分不可用导致全部不可用.后来转换到了coroutine,python 下的greenlet.所以对它的实现机制做了一个简单的了解.

每个greenlet都只是heap中的一个python object(PyGreenlet).所以对于一个进程你创建百万甚至千万个greenlet都没有问题.

typedef struct _greenlet {

 PyObject_HEAD

 char* stack_start;

 char* stack_stop;

 char* stack_copy;

 intptr_t stack_saved;

 struct _greenlet* stack_prev;

 struct _greenlet* parent;

 PyObject* run_info;

 struct _frame* top_frame;

 int recursion_depth;

 PyObject* weakreflist;

 PyObject* exc_type;

 PyObject* exc_value;

 PyObject* exc_traceback;

 PyObject* dict;

} PyGreenlet;

每一个greenlet其实就是一个函数,以及保存这个函数执行时的上下文.对于函数来说上下文也就是其stack..同一个进程的所有的greenlets共用一个共同的操作系统分配的用户栈.所以同一时刻只能有栈数据不冲突的greenlet使用这个全局的栈.greenlet是通过stack_stop,stack_start来保存其stack的栈底和栈顶的,如果出现将要执行的greenlet的stack_stop和目前栈中的greenlet重叠的情况,就要把这些重叠的greenlet的栈中数据临时保存到heap中.保存的位置通过stack_copy和stack_saved来记录,以便恢复的时候从heap中拷贝回栈中stack_stop和stack_start的位置.不然就会出现其栈数据会被破坏的情况.所以应用程序创建的这些greenlet就是通过不断的拷贝数据到heap中或者从heap中拷贝到栈中来实现并发的.对于io型的应用程序使用coroutine真的非常舒服.

下面是greenlet的一个简单的栈空间模型(from greenlet.c)

A PyGreenlet is a range of C stack addresses that must be

saved and restored in such a way that the full range of the

stack contains valid data when we switch to it.
Stack layout for a greenlet:
               |     ^^^       |

               |  older data   |

               |               |

  stack_stop . |_______________|

        .      |               |

        .      | greenlet data |

        .      |   in stack    |

        .    * |_______________| . .  _____________  stack_copy + stack_saved

        .      |               |     |             |

        .      |     data      |     |greenlet data|

        .      |   unrelated   |     |    saved    |

        .      |      to       |     |   in heap   |

 stack_start . |     this      | . . |_____________| stack_copy

               |   greenlet    |

               |               |

               |  newer data   |

               |     vvv       |

下面是一段简单的greenlet代码.

from greenlet import greenlet
def test1():

    print 12

    gr2.switch()

    print 34
def test2():

    print 56

    gr1.switch()

    print 78
gr1 = greenlet(test1)

gr2 = greenlet(test2)

gr1.switch()

目前所讨论的协程,一般是编程语言提供支持的。目前我所知提供协程支持的语言包括python,lua,go,erlang, scala和rust。协程不同于线程的地方在于协程不是操作系统进行切换,而是由程序员编码进行切换的,也就是说切换是由程序员控制的,这样就没有了线程所谓的安全问题。

所有的协程都共享整个进程的上下文,这样协程间的交换也非常方便。

相对于第二种方案(I/O多路复用),使得使用协程写的程序将更加的直观,而不是将一个完整的流程拆分成多个管理的事件处理。协程的缺点可能是无法利用多核优势,不过,这个可以通过协程+进程的方式来解决。

协程可以用来处理并发来提高性能,也可以用来实现状态机来简化编程。我用的更多的是第二个。去年年底接触python,了解到了python的协程概念,后来通过pycon china2011接触到处理yield,greenlet也是一个协程方案,而且在我看来是更可用的一个方案,特别是用来处理状态机。

目前这一块已经基本完成,后面抽时间总结一下。

总结一下:

1)多进程能够利用多核优势,但是进程间通信比较麻烦,另外,进程数目的增加会使性能下降,进程切换的成本较高。程序流程复杂度相对I/O多路复用要低。

2)I/O多路复用是在一个进程内部处理多个逻辑流程,不用进行进程切换,性能较高,另外流程间共享信息简单。但是无法利用多核优势,另外,程序流程被事件处理切割成一个个小块,程序比较复杂,难于理解。

3)线程运行在一个进程内部,由操作系统调度,切换成本较低,另外,他们共享进程的虚拟地址空间,线程间共享信息简单。但是线程安全问题导致线程学习曲线陡峭,而且易出错。

4)协程有编程语言提供,由程序员控制进行切换,所以没有线程安全问题,可以用来处理状态机,并发请求等。但是无法利用多核优势。

上面的四种方案可以配合使用,我比较看好的是进程+协程的模式。

Python 相关文章推荐
采用python实现简单QQ单用户机器人的方法
Jul 03 Python
Python中实现对Timestamp和Datetime及UTC时间之间的转换
Apr 08 Python
在Python的web框架中编写创建日志的程序的教程
Apr 30 Python
Python实现批量将word转html并将html内容发布至网站的方法
Jul 14 Python
Python正则表达式教程之一:基础篇
Mar 02 Python
用python 批量更改图像尺寸到统一大小的方法
Mar 31 Python
python 删除非空文件夹的实例
Apr 26 Python
python实现K近邻回归,采用等权重和不等权重的方法
Jan 23 Python
解析Python3中的Import
Oct 13 Python
python默认参数调用方法解析
Feb 09 Python
适合Python初学者的一些编程技巧
Feb 12 Python
基于python 等频分箱qcut问题的解决
Mar 03 Python
跟老齐学Python之数据类型总结
Sep 24 #Python
跟老齐学Python之集合的关系
Sep 24 #Python
跟老齐学Python之集合(set)
Sep 24 #Python
跟老齐学Python之有点简约的元组
Sep 24 #Python
跟老齐学Python之dict()的操作方法
Sep 24 #Python
Python单链表的简单实现方法
Sep 23 #Python
Python中bisect的用法
Sep 23 #Python
You might like
php base64 编码与解码实例代码
2017/03/21 PHP
PHP文件系统管理(实例讲解)
2017/09/19 PHP
PHP crypt()函数的用法讲解
2019/02/15 PHP
获取div编辑框,textarea,input text的光标位置 兼容IE,FF和Chrome的方法介绍
2012/11/08 Javascript
javascript 实现 秒杀,团购 倒计时展示的记录 分享
2013/07/12 Javascript
浅析offsetLeft,Left,clientLeft之间的区别
2013/11/30 Javascript
浅谈JavaScript Math和Number对象
2015/01/26 Javascript
js 能实现监听F5页面刷新子iframe 而父页面不刷新的方法
2016/11/09 Javascript
jQuery如何跳转到另一个网页 就这么简单
2016/12/28 Javascript
web打印小结
2017/01/11 Javascript
javascript实现table单元格点击展开隐藏效果(实例代码)
2017/04/10 Javascript
bootstrap fileinput实现文件上传功能
2017/08/23 Javascript
vue服务端渲染的实例代码
2017/08/28 Javascript
微信小程序组件之srcoll-view的详解
2017/10/19 Javascript
用Cordova打包Vue项目的方法步骤
2019/02/02 Javascript
react配置antd按需加载的使用
2019/02/11 Javascript
JavaScript进制转换实现方法解析
2020/01/18 Javascript
基于postman获取动态数据过程详解
2020/09/08 Javascript
解决vue项目 build之后资源文件找不到的问题
2020/09/12 Javascript
微信小程序实现分页加载效果
2020/11/19 Javascript
[58:21]DOTA2亚洲邀请赛 4.3 突围赛 Liquid vs VGJ.T 第二场
2018/04/04 DOTA
Python实现按中文排序的方法示例
2018/04/25 Python
python验证码识别教程之灰度处理、二值化、降噪与tesserocr识别
2018/06/04 Python
Python接口自动化判断元素原理解析
2020/02/24 Python
英国浴室洗脸盆购物网站:Click Basin
2018/06/08 全球购物
优质美利奴羊毛袜,不只是徒步旅行:Darn Tough Vermont
2018/11/05 全球购物
JAVA中的关键字有什么特点
2014/03/07 面试题
一道SQL面试题
2012/12/31 面试题
大学生职业生涯规划方案
2014/01/03 职场文书
小学生检讨书大全
2014/02/06 职场文书
2014年党员公开承诺书范文
2014/03/28 职场文书
医德医风演讲稿
2014/05/20 职场文书
大学生党员承诺书
2014/05/20 职场文书
2014迎国庆演讲稿
2014/09/19 职场文书
五年级上册复习计划
2015/01/19 职场文书
先进工作者个人总结
2015/02/15 职场文书