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 中文乱码问题深入分析
Mar 13 Python
使用基于Python的Tornado框架的HTTP客户端的教程
Apr 24 Python
浅析Python中的序列化存储的方法
Apr 28 Python
python计算时间差的方法
May 20 Python
详细讲解Python中的文件I/O操作
May 24 Python
Flask之flask-script模块使用
Jul 26 Python
numpy:np.newaxis 实现将行向量转换成列向量
Nov 30 Python
python关于调用函数外的变量实例
Dec 26 Python
推荐值得学习的12款python-web开发框架
Aug 10 Python
Django model class Meta原理解析
Nov 14 Python
利用python调用摄像头的实例分析
Jun 07 Python
Appium中scroll和drag_and_drop根据元素位置滑动
Feb 15 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购物车实现方法
2015/01/03 PHP
图片按比例缩放函数
2006/06/26 Javascript
javascript之dhDataGrid Ver2.0.0代码
2007/07/01 Javascript
JS获取屏幕,浏览器窗口大小,网页高度宽度(实现代码)
2013/12/17 Javascript
利用JQuery制作符合Web标准的QQ弹出消息
2014/01/14 Javascript
jQuery选择器源码解读(一):Sizzle方法
2015/03/31 Javascript
javascript模拟C#格式化字符串
2015/08/26 Javascript
javascript中加var和不加var的区别 你真的懂吗
2016/01/06 Javascript
jQuery表格插件datatables用法详解
2020/11/23 Javascript
基于JQuery实现图片轮播效果(焦点图)
2016/02/02 Javascript
AngularJS页面访问时出现页面闪烁问题的解决
2016/03/06 Javascript
JavaScript实现Base64编码转换
2016/04/23 Javascript
jQuery树形控件zTree使用小结
2016/08/02 Javascript
javascript中this用法实例详解
2017/04/06 Javascript
vue组件中的数据传递方法
2018/05/14 Javascript
微信小程序的tab选项卡的实现效果
2019/05/15 Javascript
elementui之el-tebs浏览器卡死的问题和使用报错未注册问题
2019/07/06 Javascript
ckeditor一键排版功能实现方法分析
2020/02/06 Javascript
python连接sql server乱码的解决方法
2013/01/28 Python
python网络爬虫采集联想词示例
2014/02/11 Python
python实现中文输出的两种方法
2015/05/09 Python
Pycharm编辑器技巧之自动导入模块详解
2017/07/18 Python
selenium在执行phantomjs的API并获取执行结果的方法
2018/12/17 Python
python Django编写接口并用Jmeter测试的方法
2019/07/31 Python
python元组和字典的内建函数实例详解
2019/10/22 Python
基于Python中isfile函数和isdir函数使用详解
2019/11/29 Python
Python图像处理库PIL的ImageEnhance模块使用介绍
2020/02/26 Python
HomeAway澳大利亚:预订你的度假屋,公寓、度假村、别墅等
2019/02/20 全球购物
Dogeared官网:在美国手工制作的珠宝
2019/08/24 全球购物
基层干部2014全国两会学习心得体会
2014/03/10 职场文书
村容村貌整治方案
2014/05/21 职场文书
2014年幼儿园小班工作总结
2014/12/04 职场文书
企业党员岗位承诺书
2015/04/27 职场文书
浅谈MySQL next-key lock 加锁范围
2021/06/07 MySQL
ORM模型框架操作mysql数据库的方法
2021/07/25 MySQL
一小时迅速入门Mybatis之bind与多数据源支持 Java API
2021/09/15 Javascript