使用Python中的greenlet包实现并发编程的入门教程


Posted in Python onApril 16, 2015

1   动机

greenlet 包是 Stackless 的副产品,其将微线程称为 “tasklet” 。tasklet运行在伪并发中,使用channel进行同步数据交换。

一个”greenlet”,是一个更加原始的微线程的概念,但是没有调度,或者叫做协程。这在你需要控制你的代码时很有用。你可以自己构造微线程的 调度器;也可以使用”greenlet”实现高级的控制流。例如可以重新创建构造器;不同于Python的构造器,我们的构造器可以嵌套的调用函数,而被 嵌套的函数也可以 yield 一个值。(另外,你并不需要一个”yield”关键字,参考例子)。

Greenlet是作为一个C扩展模块给未修改的解释器的。

1.1   例子

假设系统是被控制台程序控制的,由用户输入命令。假设输入是一个个字符的。这样的系统有如如下的样子:

def process_commands(*args):
  while True:
    line=''
    while not line.endswith('\n'):
      line+=read_next_char()
    if line=='quit\n':
      print "are you sure?"
      if read_next_char()!="y":
        continue  #忽略指令
    process_commands(line)

现在假设你要把程序移植到GUI,而大多数GUI是事件驱动的。他们会在每次的用户输入时调用回调函数。这种情况下,就很难实现 read_next_char() 函数。我们有两个不兼容的函数:

def event_keydown(key):
    ??

def read_next_char():
    ?? 需要等待 event_keydown() 的调用

你可能在考虑用线程实现。而 Greenlet 是另一种解决方案,没有锁和关闭问题。你启动 process_commands() 函数,分割成 greenlet ,然后与按键事件交互,有如:

def event_keydown(key):
  g_processor.switch(key)

def read_next_char():
  g_self=greenlet.getcurrent()
  next_char=g_self.parent.switch()  #跳到上一层(main)的greenlet,等待下一次按键
  return next_char

g_processor=greenlet(process_commands)
g_processor.switch(*args)
gui.mainloop()

这个例子的执行流程是: read_next_char() 被调用,也就是 g_processor 的一部分,它就会切换(switch)到他的父greenlet,并假设继续在顶级主循环中执行(GUI主循环)。当GUI调用 event_keydown() 时,它切换到 g_processor ,这意味着执行会跳回到原来挂起的地方,也就是 read_next_char() 函数中的切换指令那里。然后 event_keydown() 的 key 参数就会被传递到 read_next_char() 的切换处,并返回。

注意 read_next_char() 会被挂起并假设其调用栈会在恢复时保护的很好,所以他会在被调用的地方返回。这允许程序逻辑保持优美的顺序流。我们无需重写 process_commands() 来用到一个状态机中。

2   使用

2.1   简介

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

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

例如:

from py.magic 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()

最后一行跳转到 test1() ,它打印12,然后跳转到 test2() ,打印56,然后跳转回 test1() ,打印34,然后 test1() 就结束,gr1死掉。这时执行会回到原来的 gr1.switch() 调用。注意,78是不会被打印的。

2.2   父greenlet

现在看看一个greenlet死掉时执行点去哪里。每个greenlet拥有一个父greenlet。父greenlet在每个greenlet初 始化时被创建(不过可以在任何时候改变)。父greenlet是当greenlet死掉时,继续原来的位置执行。这样,greenlet就被组织成一棵 树,顶级的代码并不在用户创建的 greenlet 中运行,而称为主greenlet,也就是树根。

在上面的例子中,gr1和gr2都是把主greenlet作为父greenlet的。任何一个死掉,执行点都会回到主函数。

未捕获的异常会波及到父greenlet。如果上面的 test2() 包含一个打印错误(typo),他会生成一个 NameError 而干掉gr2,然后执行点会回到主函数。traceback会显示 test2() 而不是 test1() 。记住,切换不是调用,但是执行点可以在并行的栈容器间并行交换,而父greenlet定义了栈最初从哪里来。

2.3   实例

py.magic.greenlet 是一个 greenlet 类型,支持如下操作:

greenlet(run=None,parent=None)

    创建一个greenlet对象,而不执行。run是执行回调,而parent是父greenlet,缺省是当前greenlet。

greenlet.getcurrent()

    返回当前greenlet,也就是谁在调用这个函数。

greenlet.GreenletExit

    这个特定的异常不会波及到父greenlet,它用于干掉一个greenlet。

greenlet 类型可以被继承。一个greenlet通过调用其 run 属性执行,就是创建时指定的那个。对于子类,可以定义一个 run() 方法,而不必严格遵守在构造器中给出 run 参数。

2.4   切换

greenlet之间的切换发生在greenlet的 switch() 方法被调用时,这会让执行点跳转到greenlet的 switch() 被调用处。或者在greenlet死掉时,跳转到父greenlet那里去。在切换时,一个对象或异常被发送到目标greenlet。这可以作为两个greenlet之间传递信息的方便方式。例如:

def test1(x,y):
  z=gr2.switch(x+y)
  print z

def test2(u):
  print u
  gr1.switch(42)

gr1=greenlet(test1)
gr2=greenlet(test2)
gr1.switch("hello"," world")

这会打印出 “hello world” 和42,跟前面的例子的输出顺序相同。注意 test1() 和 test2() 的参数并不是在 greenlet 创建时指定的,而是在第一次切换到这里时传递的。

这里是精确的调用方式:

g.switch(obj=None or *args)

切换到执行点greenlet g,发送给定的对象obj。在特殊情况下,如果g还没有启动,就会让它启动;这种情况下,会传递参数过去,然后调用 g.run(*args) 。

垂死的greenlet

    如果一个greenlet的 run() 结束了,他会返回值到父greenlet。如果 run() 是异常终止的,异常会波及到父greenlet(除非是 greenlet.GreenletExit 异常,这种情况下异常会被捕捉并返回到父greenlet)。

除了上面的情况外,目标greenlet会接收到发送来的对象作为 switch() 的返回值。虽然 switch() 并不会立即返回,但是它仍然会在未来某一点上返回,当其他greenlet切换回来时。当这发生时,执行点恢复到 switch() 之后,而 switch() 返回刚才调用者发送来的对象。这意味着 x=g.switch(y) 会发送对象y到g,然后等着一个不知道是谁发来的对象,并在这里返回给x。

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

2.5   greenlet的方法和属性

g.switch(obj=None or *args)

    切换执行点到greenlet g,同上。

g.run

    调用可执行的g,并启动。在g启动后,这个属性就不再存在了。

g.parent

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

g.gr_frame

    当前顶级帧,或者None。

g.dead

    判断是否已经死掉了

bool(g)

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

g.throw([typ,[val,[tb]]])

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

def raiser():
    raise typ,val,tb

  g_raiser=greenlet(raiser,parent=g)
  g_raiser.switch()

2.6   Greenlet与Python线程

greenlet可以与Python线程一起使用;在这种情况下,每个线程包含一个独立的 main greenlet,并拥有自己的greenlet树。不同线程之间不可以互相切换greenlet。

2.7   活动greenlet的垃圾收集

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

如果不希望greenlet死掉或者把引用放到别处,只需要捕捉和忽略 GreenletExit 异常即可。

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

Python 相关文章推荐
Python中decorator使用实例
Apr 14 Python
python实现用户登陆邮件通知的方法
Jul 09 Python
Python实现约瑟夫环问题的方法
May 03 Python
Python统计单词出现的次数
Apr 04 Python
django Serializer序列化使用方法详解
Oct 16 Python
在linux系统下安装python librtmp包的实现方法
Jul 22 Python
Django的Modelforms用法简介
Jul 27 Python
python 使用pdfminer3k 读取PDF文档的例子
Aug 27 Python
python selenium实现发送带附件的邮件代码实例
Dec 10 Python
python实现ip地址的包含关系判断
Feb 07 Python
python requests.get带header
May 05 Python
详解python 内存优化
Aug 17 Python
利用Python的Twisted框架实现webshell密码扫描器的教程
Apr 16 #Python
使用Python的Twisted框架实现一个简单的服务器
Apr 16 #Python
使用Python的Twisted框架编写简单的网络客户端
Apr 16 #Python
从Python的源码浅要剖析Python的内存管理
Apr 16 #Python
用Python实现换行符转换的脚本的教程
Apr 16 #Python
Python下的subprocess模块的入门指引
Apr 16 #Python
Python下的twisted框架入门指引
Apr 15 #Python
You might like
用PHP实现维护文件代码
2007/06/14 PHP
Uncaught exception com_exception with message Failed to create COM object
2012/01/11 PHP
php中base_convert()进制数字转换函数实例
2014/11/20 PHP
微信JSSDK分享功能图文实例详解
2019/04/08 PHP
php5.3/5.4/5.5/5.6/7常见新增特性汇总整理
2020/02/27 PHP
基于thinkphp5框架实现微信小程序支付 退款 订单查询 退款查询操作
2020/08/17 PHP
ASP中用Join和Array,可以加快字符连接速度的代码
2007/08/22 Javascript
JS和jquery获取各种屏幕的宽度和高度的代码
2013/08/02 Javascript
js实现新年倒计时效果
2015/12/10 Javascript
javascript产生随机数方法汇总
2016/01/25 Javascript
JS实现回到页面顶部动画效果的简单实例
2016/05/24 Javascript
Angularjs实现带查找筛选功能的select下拉框示例代码
2016/10/04 Javascript
原生js实现放大镜效果
2017/01/11 Javascript
jquery uploadify隐藏上传进度的实现方法
2017/02/06 Javascript
基于JavaScript实现瀑布流效果
2017/03/29 Javascript
JavaScript手风琴页面制作
2017/05/17 Javascript
关于jQuery.ajax()的jsonp碰上post详解
2017/07/02 jQuery
微信小程序实现文字无限轮播效果
2018/12/28 Javascript
利用Promise自定义一个GET请求的函数示例代码
2019/03/20 Javascript
解决vuex刷新状态初始化的方法实现
2019/08/15 Javascript
js生成1到100的随机数最简单的实现方法
2020/02/07 Javascript
深入了解Vue动态组件和异步组件
2021/01/26 Vue.js
[04:52]DOTA2亚洲邀请赛附加赛 TOP10精彩集锦
2015/01/29 DOTA
[01:07:11]Secret vs Newbee 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
zbar解码二维码和条形码示例
2014/02/07 Python
Python实现抓取网页并且解析的实例
2014/09/20 Python
Python实现手写一个类似django的web框架示例
2018/07/20 Python
Python3最长回文子串算法示例
2019/03/04 Python
小米官方旗舰店:Xiaomi
2020/08/07 全球购物
怎样写好自荐信和推荐信
2013/12/26 职场文书
生产厂长岗位职责
2014/02/21 职场文书
春节晚会主持词
2014/03/24 职场文书
爱心捐款倡议书
2014/04/14 职场文书
幼儿园托班教育随笔
2015/08/14 职场文书
python绘图subplots函数使用模板的示例代码
2021/04/30 Python
Python几种酷炫的进度条的方式
2022/04/11 Python