Greenlet简介

一个 “greenlet” 是一个很小的独立微线程。可以把它想像成一个堆栈帧,栈底是初始调用,而栈顶是当前greenlet的暂停位置。你使用greenlet创建一堆这样的堆 栈,然后在他们之间跳转执行。跳转不是绝对的:一个greenlet必须选择跳转到选择好的另一个greenlet,这会让前一个挂起,而后一个恢复。两 个greenlet之间的跳转称为 切换(switch) 。

当你创建一个greenlet,它得到一个初始化过的空堆栈;当你第一次切换到它,他会启动指定的函数,然后切换跳出greenlet。当最终栈底 函数结束时,greenlet的堆栈又编程空的了,而greenlet也就死掉了。greenlet也会因为一个未捕捉的异常死掉。

greenlet又叫协程,是一个伪线程,实际上是串行执行的,其实greenlet不是一种真正的并发机制,而是在同一线程内,在不同函数的执行代码块之间切换,实施“你运行一会、我运行一会”,并且在进行切换时必须指定何时切换以及切换到哪。greenlet的接口是比较简单易用的,但是使用greenlet时的思考方式与其他并发方案存在一定区别。线程/进程模型在大逻辑上通常从并发角度开始考虑,把能够并行处理的并且值得并行处理的任务分离出来,在不同的线程/进程下运行,然后考虑分离过程可能造成哪些互斥、冲突问题,将互斥的资源加锁保护来保证并发处理的正确性。greenlet则是要求从避免阻塞的角度来进行开发,当出现阻塞时,就显式切换到另一段没有被阻塞的代码段执行,直到原先的阻塞状况消失以后,再人工切换回原来的代码段继续处理。因此,greenlet本质是一种合理安排了的串行,greenlet能够得到比较好的性能表现,主要也是因为通过合理的代码执行流程切换,完全避免了死锁和阻塞等情况。因为greenlet本质是串行,因此在没有进行显式切换时,代码的其他部分是无法被执行到的,如果要避免代码长时间占用运算资源造成程序假死,那么还是要将greenlet与线程/进程机制结合使用(每个线程、进程下都可以建立多个greenlet,但是跨线程/进程时greenlet之间无法切换或通讯)。

 from greenlet import greenlet

 def test1():
print 12
gr2.switch()
print 34
gr2.switch() def test2():
print 56
gr1.switch()
print 78 gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

上面是一个greenlet的例子 ,有助于我们理解greenlet的概念 。

输出为 12 56 34 ,

Greenlet属性和操作

理解上面的程序先要学习greenlet的操作有哪些:

  greenlet(run=None,parent=None): 创建一个greenlet对象,而不执行。run是回调函数,而parent是父greenlet,缺省是当前greenlet

  greenlet.getcurrent():获取当前greenlet,也就是谁在调用这个函数。

  greenlet.GreenletExit:用户杀死一个greenlet,这个异常不会波及到父greenlet。

注意上面的greenlet是指库 greenlet

下面是greenlet对象的方法和属性:

  g.switch(*args, **kwargs) : 切换执行点到 greenlet对象 g 上。

  g.run 调用g,并启动。在g启动后,这个属性就不存在了。

  g.parent g的父greenlet。可写的,但是不允许创建循环的父关系。

  g.gr_frame 当前帧,或者None

  g.dead 判断g是否已经死掉了。

  bool(g) 如果g是活跃的返回True,如果尚未启动或者结束后返回False。

  g.throw([typ,[val,[tb]]])切换执行点到 g,但是立即抛出指定的异常到g。如果没有提供参数,异常缺省就是greenlet.GreenletExit。如上面描述的,就是异常波及规则。注意调用这个方法等同于如下:

def raiser():
raise typ,val,tb g_raiser = greenlet(raiser,parent=g)
g_raiser.switch()

关于切换 ,switch()方法是greenlet重要的组成部分。switch()用于greenlet之间的切换,当方法被调用时,这会让执行点跳转到greenlet的switch()被调用处。或者在greenlet死掉时,跳转到父greenlet那里去。在切换时,一个对象或者异常被发送到目标greenlet。例如 :

from greenlet import greenlet

def test1(x,y):
z= gr2.switch(x+y)
print "z",z
print "test1" def test2(u):
print "u",u
gr1.switch(42,20)
print 'test2' gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch("hello"," world")

结果:

u hello world
z (42, 20)
test1
test2

如果一个greenlet的run()结束了,他会返回值到父greenlet.如果run()是因异常而终止的,异常会波及到父 greenlet(除非是greenlet.GreenletExit异常,这种情况下,异常会被捕捉并返回到父greenlet)。目标greenlet会接收到发送来的对象作为 switch() 的返回值。虽然 switch() 并不会立即返回,但是它仍然会在未来某一点上返回,当其他greenlet切换回来时。当这发生时,执行点恢复到 switch() 之后,而 switch() 返回刚才调用者发送来的对象。这意味着 x=g.switch(y) 会发送对象y到g,然后等着一个不知道是谁发来的对象,并在这里返回给x。

注意上面的z= gr2.switch(x+y),这儿就是把x = “hello” y=“ world”发送到 gr2,然后在test2中 gr1.switch(40,20)返回给 z,所以输出的z 是(40,20).这儿还是比较绕的。

注意,任何切换到死掉的greenlet的行为都会切换到greenlet的父greenlet,或者父的父 ,等等。最终的父是永远不会死掉的”main“ greenlet。

Greenlet与Python线程

  greenlet可以和线程一起使用,在这种情况下,每个线程包含一个独立的main greenlet,并拥有自己的greenlet子树。但是不同的线程之间不能切换greenlet。我觉得提高性能的方法就是“多进程+多线程+协程”这里面是一层包含一层的概念。对于python而言,由于GIL的限制,多线程是无法使用多核CPU的。

Greenlet的垃圾回收机制

  如果不再有对greenlet对象的引用时(包括其他greenlet的parent),然后没有办法切换回greenlet。在这种情况下会生成一个 GreenletExit 异常到greenlet。这是greenlet异步接收异常的唯一情况。这给出一个 try .. finally 用于清理greenlet内的资源。这个功能同时允许greenlet中无限循环等待数据和处理数据的编程风格。这样循环可以在最后一个引用消失时自动中断。

  如果希望greenlet死掉或者通过一个新的引用复活,只需要捕捉和忽略 GreenletExit 异常即可达到无限循环。(这句话我的理解是,捕捉GreenletExit就会导致greenlet死掉,忽略会导致greenlet复活)

  greenlet不参与垃圾收集;greenlet帧的循环引用数据会被检测到。将引用传递到其他的循环greenlet会引起内存泄露。

C API 引用

  Greenlet可以由C和C++编写的扩展模块或者嵌入python 的程序进行创建和操作。greenlet.h头提供了可供纯python模块调用的全部的API。

Types

Type name Python name
PyGreenlet greenlet.greenlet

Exceptions

Type name Python name
PyExc_GreenletError greenlet.error
PyExc_GreenletExit greenlet.GreenletExit

Reference

PyGreenlet_Import()

一个宏指令,引入greenlet模块,初始化C API。一旦扩展模块使用了greenlet的C API 就必须调用这个。

int PyGreenlet_Check(PyObject *p)

宏指令,如果参数 p是PyGreenlet就返回true

int PyGreenlet_STARTED(PyGreenlet *g)

宏指令,如果greenlet g 已经启动 返回true

int PyGreenlet_ACTIVE(PyGreenlet *g)

宏指令,如果greenlet 对象 g已经启动且没有死亡 返回true

PyGreenlet *PyGreenlet_GET_PARENT(PyGreenlet *g)

宏指令,返回 g的父 greenlet

PyGreenlet *PyGreenlet_SetParent(PyGreenlet *g)

设置 g的父greenlet,如果成功返回0.如果返回-1,g并不是一个指向PyGreenlet的指针 而且会触发 Attribute Error。

PyGreenlet *PyGreenlet_GetCurrent(void)

返回现在活跃状态的greenlet对象

PyGreenlet *PyGreenlet_New(PyObject *run,PyObject *parent)

用于生成一个greenlet对象,参数 run 和 parent都是可选的。如果run 为空,那么greenlet仍然会生成,但是switch的时候就会失败。如果parent为空,则缺省为当前greenlet对象为父。

PyGreenlet *PyGreenlet_Switch(PyGreenlet *g, PyObject *args, PyObject *kwargs)

切换到greenlet对象 g, 后两个参数可为空。如果args为空,那么一个空元素将传递给目标greenlet。如果kwargs为空,那么没有关键参数被传递给目标greenlet。如果参数指定,那么args必定是元祖,kwargs是字典。

PyObject *PyGreenlet_Throw(PyGreenlet *g,PyObject *typ, PyObject *val,PyObject *tb)

切换到greenlet g,但是立即触发异常。typ是异常类型,val是值,tb是可选的,代表回溯对象,可为空。

关于eventlet 和 gevent 这两个以greenlet为核心的并发框架。两个之间的比较 http://blog.gevent.org/2010/02/27/why-gevent/ ,gevent以libevent为核心,最新版本是libev,eventlet主要以纯python写的事件循环为基础,而且在socket方面存在缺陷。不过我认为年代久远,不足以作为凭证,说不定最新的版本的eventlet已经解决了bug。

gevent学习:见英文版http://sdiehl.github.io/gevent-tutorial/ 中文版http://xlambda.com/gevent-tutorial/

eventlet学习 :见 http://eventlet.net/doc/index.html

后续的学习中我将对两个框架进行实践。当然如果使用twisted也是一个很好的解决方案。

来源:

http://greenlet.readthedocs.org/en/latest/

http://gashero.yeax.com/?p=112

http://www.elias.cn/Python/PyConcurrency?from=Develop.PyConcurrency

greenlet微线程的更多相关文章

  1. 循环引擎 greenlet 没有显式调度的微线程,换言之 协程

    小结: 1. micro-thread with no implicit scheduling; coroutines, in other words. 没有显式调度的微线程,换言之 协程 2. 一个 ...

  2. based on Greenlets (via Eventlet and Gevent) fork 孙子worker 比较 gevent不是异步 协程原理 占位符 placeholder (Future, Promise, Deferred) 循环引擎 greenlet 没有显式调度的微线程,换言之 协程

    gevent GitHub - gevent/gevent: Coroutine-based concurrency library for Python https://github.com/gev ...

  3. paip.提升性能---协程“微线程”的使用.

    paip.提升性能---协程的使用. 近乎无限并发的"微线程" 作者Attilax  艾龙,  EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:h ...

  4. C# 【一】进程 , 线程 , 微线程 , 同步 , 异步 , 并发 , 并行 , 阻塞 , 非阻塞

    一 理解篇 前言 本文仅仅用作借鉴使用,作者刚入行不久,所以请不小心看到这篇文章的朋友,手下留情. 本文以小故事的形式进行叙述,逻辑不通之处.请理解. 如有错误 ,欢迎指出. 谢谢.          ...

  5. Python中greenlet和gevent使用示例

    目录 greenlet示例 示例1,线程切换 示例2 gevent 示例1 示例2: gevent使用monkey对所有系统自带的IO操作打patch 示例3,发送请求 示例4:使用gevent的so ...

  6. Python实例手册

    在电脑中突然发现一个这么好的资料,雪松大神制作,不敢独享,特与大家共享.连他的广告也一并复制了吧! python实例手册 #encoding:utf8 # 设定编码-支持中文 0说明 手册制作: 雪松 ...

  7. 【转】Locust-工具核心原理分析

    Locust工具在市场上不如Loadrunner / JMeter流行,使用的范围也没有那么广,但不可否认其是一款很不错的工具.我个人觉得Locust使用不是那么广泛,主要是因为一下方式: Locus ...

  8. Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就绪,挂起,运行) ,***协程概念,yield模拟并发(有缺陷),Greenlet模块(手动切换),Gevent(协程并发)

    Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就 ...

  9. 线程回调,线程中的队列,事件,greenlet模块,gevent模块,自定义补丁, 单线程实现并发,协程

    1.线程回调 在线程池/进程池每次提交任务,都会返回一个表示任务的对象,Future对象Future对象具备一个绑定方法,add_done_callback 用于指定回调函数 add 意味着可以添加多 ...

随机推荐

  1. iframe参数

    iframe参数: <iframe src="test.jsp" width="100″ height="50″ frameborder="no ...

  2. 前端学习笔记(zepto或jquery)——对li标签的相关操作(三)

    对li标签的相关操作——八种方式遍历li标签并获取其值 $("ul>li").forEach(function(item,index){ alert(index+" ...

  3. 自己定义View之绘制圆环

    一.RingView 自己定义的view,构造器必须重写,至于重写哪个方法,參考例如以下: ①假设须要改变View绘制的图像,那么须要重写OnDraw方法.(这也是最经常使用的重写方式.) ②假设须要 ...

  4. [ 夜间模式 ] NightVersion

    DKNightVersion框架.重写管理类 & 控件的分类!--可重写{ 使用GCD.runtime.delegate等 & 工具类的创建 } ================ 1. ...

  5. 【百度地图API】今日小年大进步,齐头共进贺佳节——API优化升级上线,不再增加内存消耗

    原文:[百度地图API]今日小年大进步,齐头共进贺佳节--API优化升级上线,不再增加内存消耗 任务描述: 今天是2011年01月26日,小年夜.百度地图API在小年夜献给广大API爱好者一份给力的礼 ...

  6. 所谓策略,我站在旁边看今天 神刻的样子O2O

    雕爷.何许人也? 卖牛腩的大叔? 卖精油的大爷?还是卖烤肉的家伙? 事实上以上答案都是肯定的,他就是阿芙精油,雕爷牛腩创业神话的缔造者.那么雕爷是怎样取得这种创业成功的呢?前段时间我还不清楚雕爷的厉害 ...

  7. [顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功)

    原文:[顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功) [顶]ORACLE PL/SQL编程详解之二: PL/SQL块结构和组成元素(为山九仞,岂一日 ...

  8. request.getparameter和 request.getattribute的差别

    request.getAttribute():是request时设置的变量的值,用request.setAttribute("name","您自己的值");来设 ...

  9. SQL点滴12—SQL Server备份还原数据库中的小把戏

    原文:SQL点滴12-SQL Server备份还原数据库中的小把戏 备份数据库时出现一个不太了解的错误 ,错误信息“is formatted to support  1 media families, ...

  10. Atitit.升级软件的稳定性---基于数据库实现持久化 循环队列 循环队列

    Atitit.升级软件的稳定性---基于数据库实现持久化  循环队列 环形队列 1. 前言::选型(马) 1 2. 实现java.util.queue接口 1 3. 当前指针的2个实现方式 1 1.1 ...