py.magic.greenlet: Lightweight concurrent programming
2011-10-07 22:21 prim 阅读(521) 评论(0) 收藏 举报原文地址:py.magic.greenlet: lightweight concurrent programming
1 Motivation
greenlet算是Stackless的开发过程中的副产品。(Stackless是一个CPython版本,支持某种被称为'tasklets'微线程的;多个tasklet看起来像是并发运行的,运行在一个或几个OS级的线程中;tasklet可以使用channel同步交换数据)
比起tasklets,greenlet更像是一种不带隐藏调度(就是不是你自己可以操控的调度)更为原始的微线程。你也可以称greenlet为coroutine协程。greenlet在你想完全操控程序的控制流时特别有用:你可以在greenlet之上建立自己的微线程调度机制。但是,greenlet用来做更高级的控制流结构。例如,我们可以自己打造生成器;不同于Python内置的生成器,我们可以在自己的生成器中内嵌函数,而且内嵌函数也可以生成值(你甚至不需要用到yield关键字,代码例子:http://codespeak.net/py/0.9.2/apigen/source/c-extension/greenlet/test_generator.py.html)。
greenlet是一个C扩展模块,提供给正常的不需要修改(hack)的解释器使用。
1.1 Example
假设我们现在有一段terminal-like console程序代码:用户输入命令,然后程序执行命令。命令由一个一个字符组成。在这么一个程序中,通常都会这么一个循环:
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 # ignore the command
process_command(line)
如果你想要为这个程序加上图形界面 GUI。大多数GUI框架都是基于事件的,有个事件循环的部分。用户每输入一个字符就会调用一个回调函数。在这种情况下,控制流不应该一直停在read_next_char(),而是停在GUI的事件循环中。
也就是说,在使用GUI框架的情况下,我们会有两个不相容的函数:
def event_keydown(key):
# GUI框架在有键被按下时调用
# GUI框架有自己的事件循环:等候事件,处理事件
# 与命令行的输入处理方式矛盾,你可能一个程序执行流卡在两个地方!!!
def read_nex_char():
不改变原有的console程序代码而又要加上图形界面,通常可以使用多线程来解决。不过greenlet是一种更好解决方法,没有多线程同步的那些麻烦。你可以一开始建立一个greenlet运行process_commands();改写read_next_char(),在read_next_char()中将控制流转回主greenlet中的GUI部分执行,当GUI框架调用到event_keydown(key)时又将控制流转回process_commands()的greenlet。
def event_keydown(key):
# 5.有键盘输入时,又将控制流转回协程g_processor
g_processor.switch(key)
# 改写read_next_char()
def read_next_char():
g_self = greenlet.getcurrent()
# 3.命令行程序代码段调用read_next_char()由将控制流转回到main协程
next_char = g_self.parent.switch()
return next_char
# 1.创建一个协程执行原本的命令行程序代码段,这个协程暗含一个父协程(就是执行当前代码的协程,叫作main协程吧),
g_processor = greenlet(process_commands)
# 2.将控制流传去执行命令行程序的代码段
g_processor.switch(*args) # input arguments to process_commands()
# 4.mian协程开始执行GUI框架的事件循环
gui.mainloop()
# 通过改写read_next_char()函数,原本的命令行程序代码不需要做什么改变就能获得图形界面,牛逼不!!!
在这个例子里,控制流的执行过程是这样的:

2 Usage
2.1 Introduction
你可以把greenlet当作一个轻量级的假线程。你可以把他当作是一个小型的栈帧;最外层(或者说最底层)的帧是你调用的initial function,最里层的帧是某个当前执行的greenlet。你可以通过greenlet创建这些栈帧,在这些栈帧中转转执行。这些转转都是显式的:一个greenlet转到另外一个greenlet时,会造成原来的greenlet栈帧被挂起,以后再被恢复。我们把greenlet之间的转转叫做"switching"。
当你创建一个greenlet时,你就得到一个空的栈;你通过它调用switch()时,就会开始执行某个特定的函数(创建greenlet时提供的,在这个函数中你也可以通过switch()将控制流转到其他greenlet)。当最外层的函数终止执行时,greenlet的栈再次清空,greenlet的状态变为"dead"。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()
最后1行将控制流转到test1(),test1()打印12,然后转到test2(),test()打印56,然后再转回test1(),test1()被刮起的帧被恢复,接着打印34,test1()终止执行,gr1死亡。这个时候控制流会转回gr1.switch()。需要注意的是test2()的print 78没有被执行到。
2.2 Parent
每一个协程都拥有父协程,协程g指定的函数执行完毕时,就将控制流转会g的父协程去,而不是调用g.switch()的地方。调用greenlet(run=None, parent=None)创建协程时,父协程默认为当前执行的协程。最外层代码的暗指一个main协程。
协程中没有捕获的异常会传给父协程。
要记住greenlet.switch()并不是个函数调用,而是将控制流转移的方法。
2.3 Instantiation
py.magic.greenlet中:
- greenlet(run=None, parent=None)
创建一个协程,并不立即执行。run指定要执行的函数,不指定parent的话就父协程为当前执行的协程。 - greenlet.getcurrent()
返回当前执行的协程。 - greenet.GreenletExit
唯一一个不传给父协程的异常:用来杀死一个协程。
2.4 Switching
有某个协程g,调用g.switch()时会将控制流转到g上;如果g已经死亡的话,就将执行流转到g的父协程去。
switch()的参数可以用来向目标协程传递数据。当目标协程还没有开始执行时,就作为执行函数的参数;如果已经开始执行,就将参数当作目标协程上一次将控制流swtich()出去的返回值。
例子:
def test1(x, y): # 1. x = "hello", y = " world"
z = gr2.switch(x+y) # 4. z = 42
print z
def test2(u): # 2. u = "hello world"
print u
gr1.switch(42) # 3. 传42给协程gr1
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch("hello", " world")
- g.switch(obj=None or *args)
- Dying greenlet
如果greenlet的run执行完毕,那么run()的返回值就会传给父协程(有点像这样father.switch(runs_return_value))。
2.5 Methods and attributes of greenlets
- g.switch(obj=None or *args)
- g.run
g要执行的函数对象,g一旦开始运行,这个属性就不再存在 - g.parent
可以改写,但是不允许父协程间有循环派生的关系 - g.gr_frame
XXX - g.dead
如果g已死,True;否则None - bool(g)
g在运行中则True;还未运行或运行完(死亡)就为Fasle - g.throw([typ, [val, [tb]]])
将控制流转到g,并立即在g中抛出给定的异常。没有给定异常时,抛出greenlet.GreenletExit。
2.6 Greenlets and Python threads
协程可以与Python线程组合使用;每一个Python线程都包含一个main协程和一族子协程,但是我们不可以跨线程作协程的跳转。
2.7 Garbage-collecting live greenlets
If all the references to a greenlet object go away (including the references from the parent attribute of other greenlets), then there is no way to ever switch back to this greenlet. In this case, a GreenletExit exception is generated into the greenlet. This is the only case where a greenlet receives the execution asynchronously. This gives try:finally: blocks a chance to clean up resources held by the greenlet. This feature also enables a programming style in which greenlets are infinite loops waiting for data and processing it. Such loops are automatically interrupted when the last reference to the greenlet goes away.
The greenlet is expected to either die or be resurrected by having a new reference to it stored somewhere; just catching and ignoring the GreenletExit is likely to lead to an infinite loop.
Greenlets do not participate in garbage collection; cycles involving data that is present in a greenlet's frames will not be detected. Storing references to other greenlets cyclically may lead to leaks.
浙公网安备 33010602011771号