greenlet微线程
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微线程的更多相关文章
- 循环引擎 greenlet 没有显式调度的微线程,换言之 协程
小结: 1. micro-thread with no implicit scheduling; coroutines, in other words. 没有显式调度的微线程,换言之 协程 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 ...
- paip.提升性能---协程“微线程”的使用.
paip.提升性能---协程的使用. 近乎无限并发的"微线程" 作者Attilax 艾龙, EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:h ...
- C# 【一】进程 , 线程 , 微线程 , 同步 , 异步 , 并发 , 并行 , 阻塞 , 非阻塞
一 理解篇 前言 本文仅仅用作借鉴使用,作者刚入行不久,所以请不小心看到这篇文章的朋友,手下留情. 本文以小故事的形式进行叙述,逻辑不通之处.请理解. 如有错误 ,欢迎指出. 谢谢. ...
- Python中greenlet和gevent使用示例
目录 greenlet示例 示例1,线程切换 示例2 gevent 示例1 示例2: gevent使用monkey对所有系统自带的IO操作打patch 示例3,发送请求 示例4:使用gevent的so ...
- Python实例手册
在电脑中突然发现一个这么好的资料,雪松大神制作,不敢独享,特与大家共享.连他的广告也一并复制了吧! python实例手册 #encoding:utf8 # 设定编码-支持中文 0说明 手册制作: 雪松 ...
- 【转】Locust-工具核心原理分析
Locust工具在市场上不如Loadrunner / JMeter流行,使用的范围也没有那么广,但不可否认其是一款很不错的工具.我个人觉得Locust使用不是那么广泛,主要是因为一下方式: Locus ...
- Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就绪,挂起,运行) ,***协程概念,yield模拟并发(有缺陷),Greenlet模块(手动切换),Gevent(协程并发)
Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就 ...
- 线程回调,线程中的队列,事件,greenlet模块,gevent模块,自定义补丁, 单线程实现并发,协程
1.线程回调 在线程池/进程池每次提交任务,都会返回一个表示任务的对象,Future对象Future对象具备一个绑定方法,add_done_callback 用于指定回调函数 add 意味着可以添加多 ...
随机推荐
- iframe参数
iframe参数: <iframe src="test.jsp" width="100″ height="50″ frameborder="no ...
- 前端学习笔记(zepto或jquery)——对li标签的相关操作(三)
对li标签的相关操作——八种方式遍历li标签并获取其值 $("ul>li").forEach(function(item,index){ alert(index+" ...
- 自己定义View之绘制圆环
一.RingView 自己定义的view,构造器必须重写,至于重写哪个方法,參考例如以下: ①假设须要改变View绘制的图像,那么须要重写OnDraw方法.(这也是最经常使用的重写方式.) ②假设须要 ...
- [ 夜间模式 ] NightVersion
DKNightVersion框架.重写管理类 & 控件的分类!--可重写{ 使用GCD.runtime.delegate等 & 工具类的创建 } ================ 1. ...
- 【百度地图API】今日小年大进步,齐头共进贺佳节——API优化升级上线,不再增加内存消耗
原文:[百度地图API]今日小年大进步,齐头共进贺佳节--API优化升级上线,不再增加内存消耗 任务描述: 今天是2011年01月26日,小年夜.百度地图API在小年夜献给广大API爱好者一份给力的礼 ...
- 所谓策略,我站在旁边看今天 神刻的样子O2O
雕爷.何许人也? 卖牛腩的大叔? 卖精油的大爷?还是卖烤肉的家伙? 事实上以上答案都是肯定的,他就是阿芙精油,雕爷牛腩创业神话的缔造者.那么雕爷是怎样取得这种创业成功的呢?前段时间我还不清楚雕爷的厉害 ...
- [顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功)
原文:[顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功) [顶]ORACLE PL/SQL编程详解之二: PL/SQL块结构和组成元素(为山九仞,岂一日 ...
- request.getparameter和 request.getattribute的差别
request.getAttribute():是request时设置的变量的值,用request.setAttribute("name","您自己的值");来设 ...
- SQL点滴12—SQL Server备份还原数据库中的小把戏
原文:SQL点滴12-SQL Server备份还原数据库中的小把戏 备份数据库时出现一个不太了解的错误 ,错误信息“is formatted to support 1 media families, ...
- Atitit.升级软件的稳定性---基于数据库实现持久化 循环队列 循环队列
Atitit.升级软件的稳定性---基于数据库实现持久化 循环队列 环形队列 1. 前言::选型(马) 1 2. 实现java.util.queue接口 1 3. 当前指针的2个实现方式 1 1.1 ...