版本18和19间的区别
于2010-01-05 10:05:25修订的的版本18
大小: 13563
编辑: setnip02
备注:
于2010-01-05 19:35:48修订的的版本19
大小: 13367
编辑: 116
备注:
删除的内容标记成这样。 加入的内容标记成这样。
行号 169: 行号 169:

In practice, it is very rare that a tasklet will run without yielding to allow others to run. More often than not, tasklets are blocked waiting for events to occur often enough that they do not need to explicitly yield. But occasionally unforeseen situations can occur where code paths lead to yields not being hit, or perhaps bad code enters an infinite loop.

With this in mind, it is often more useful to take advantage of the pre-emptive scheduling functionality, to detect long running tasklets. The way this works is to pass in a sufficiently high timeout that the only tasklets which hit it are those which are not yielding.

Idiom - detecting uncooperative tasklets:
在实践中,很少会出现一个小任务一直运行而不让其他小任务运行。小任务常常被事件阻塞而自动让出执行权,而不需要显式的去让出来。但是偶尔有预料之外的情况发生,导致程序不能执行到让出执行权的代码或者代码进入了一个死循环。

基于这样的考虑,常常会利用抢占式调度的功能,来检测长时间运行的小任务。办法是设置一个足够大的超时时间,而碰到这个超时的只可能是那些没有让出执行权的代码。

习惯用法 - 检测不合作的小任务:
行号 185: 行号 184:
协作式调度,但是抢占 but pre-empting uncooperative tasklets: 协作式调度,但是抢占不合作的小任务:
行号 189: 行号 188:
As most tasklets will yield before having executed 1000000 instructions, the only tasklets which will be interrupted and returned will be those that are not yielding and therefore being uncooperative.
因为大部分小任务会在执行1000000条指令后让出执行权,而只有那些不让出执行权、不合作的小任务会被中断并返回。
行号 196: 行号 196:
Interrupted tasklets are no longer in the scheduler. We do not know what this tasklet was doing, and to leave it uncompleted may depending on our application be unacceptable. The call to tasklet.insert() puts the it at the end of the list of runnable tasklets in the scheduler, forcibly ensuring the others have a chance to run before it gets another.

It might also be reasonable to assume that any tasklet that gets interrupted in this manner is behaving wrong, and that to kill it having recorded as much information about it as possible (like its call stack) before doing so is better.

中断的小任务不会继续留在调度器中。我们不知道这个小任务是做什么的,根据应用程序的需求不让它执行完可能是无法接受的,调用 tasklet.insert()将它重新插回调度器中,放在所有可以运行的小任务的列表末尾,来保证在这个小任务下一次被执行前,其他小任务有机会运行。

如果可以假定这样被中断的小任务都是有问题的,那么可以杀死它。在杀死之前记录下足够的信息(比如调用栈)就更好了。

英文原版: http://www.disinterest.org/resource/stackless/2.6.4-docs-html/stackless-python.html

Stackless Python

Stackless Python 是Python语言的一个增强版本。它让程序员可以获得基于线程的程序的优点,同时又避免传统线程带来的性能和复杂度问题。Stackless Python为Python语言添加的微线程(microthread)是一种方便、廉价、轻量级的工具,如果使用得当,它不仅可以提供一种构建应用程序或者框架的方法,而且能改进程序的结构和可读性。

如果你在你安装的Python附带的文档中读到这篇文章,这说明你安装的已经是Stackless Python而非标准的Python。

1. 概述

除了Stackless Python新增的功能部分,Stackless Python的其他部分的行为和标准的Python完全一样,用法也完全一样。Stackless的新增的功能,是通过stackless模块暴露出来的框架来使用的。

2. 你需要知道的

Stackless Python只提供了一个最基本的框架,它没有附带任何支撑功能,只是满足构建一个特定用途的框架时可能出现的一般需求。

2.1. 阻塞的操作

如果调用的操作会阻塞Python解释器,用户需要注意,这个操作也会阻塞所有运行中的小任务。Python解释器会一直阻塞,调度器也会阻塞在执行操作的小任务上,直到那个小任务结束。阻塞解释器的操作常常是和同步IO(文件读写、套接字操作、进程间通讯等)有关的,也要注意time.sleep()。建议用户使用异步版本的IO函数。

某些第三方模块可以用Stackless兼容的方式来代替一些标准库中模块。这种方法的好处是,原来使用标准模块的其他模块也可以在替代的模块上工作。Stackless socket模块是最常使用的替代模块。

2.2. 异常

小任务里面出现的某些异常,是期望能沿着调用栈向上直到调度器的。这意味着简单的使用except语句可能会导致难以查出的问题。

有关这个问题的描述可以参看TaskletExit异常。

2.3. 调试

Stackless的调度机制改变了Python调试挂钩的工作方式。调试挂钩需要以小任务为单位设置,而不是线程。但是很少有调试器(标准库中一个也没有)考虑了这一点。这导致如果不进行一些特殊处理将不能进行调试。

这个问题的详细描述,参看Stackless调试文档。

3. 外部资源

除了此文档外,还有一系列资源:

  • Stackless邮件列表
  • Stackless样例
  • Grant Olson的入门教程:Introduction to Concurrent Programming with Stackless Python.

4. 历史

Continuations are a feature that require the programming language they are part of, to be implemented in a way conducive to their presence. In order to add them to the Python programming language, Christian Tismer modified it extensively. The primary way in which it was modified, was to make it Stackless. And so, his fork of Python was named Stackless Python.

Now, storing data on the stack locks execution on an operating system thread to the current executing functionality, until that functionality completes and the stack usage is released piece by piece. In order to add continuations to Python, that data needed to be stored on the heap instead, therefore decoupling the executing functionality from the stack. With the extensive changes this required in place, Christian released Stackless Python.

Maintaining the fork of a programming language is a lot of work, and when the programming language changes in ways that are incompatible with the changes in the fork, then that work is sigificantly increased. Over time it became obvious that the amount of changes to Python were too much weight to carry, and Christian contemplated a rewrite of Stackless. It became obvious that a simpler approach could be taken, where Stackless was no longer stackless and no longer had continuations.

Following the rewrite, a framework was designed and added inspired by coming from CSP and the Limbo programming language. From this point on, Stackless was in a state where it contained the minimum functionality to give the benefits it aimed to provide, with the minimum amount of work required to keep it maintained.

A few years later in 2004, while sprinting on Stackless in Berlin, Christian and Armin Rigo came up with a way to take the core functionality of Stackless and build an extension module that provided it. This was the creation of greenlets, which are very likely a more popular tool than Stackless itself today. The greenlet source code in practice can be used as the base for green threading functionality not just in Python, but in other programming languages and projects.

With Stackless Python a solid product, Christian’s focus moved onto other projects, PyPy among them. One of his interests in PyPy was a proper implementation of the Stackless functionality, where it could be integrated as a natural part of any Python built.

For a while, Stackless Python languished, with no new versions to match the releases of Python itself. Then in 2006, CCP sent Kristjan Valur Jonsson and Richard Tew to PyCon where they sprinted with the aid of Christian Tismer. The result was an up to date release of Stackless Python. From this point in time, maintaining and releasing Stackless Python has been undertaken by Richard and Kristjan.

stackless — 内置的扩展模块

使用stackless模块是程序员使用Stackless Python的增强功能的方法。

1. 函数

2. 属性

3. 异常

小任务(Tasklet) — 轻量级的线程

小任务将函数包装起来,允许函数以微线程来加载并在调度器中执行

加载一个小任务:

stackless.tasklet(callable)(*args, **kwargs)

这是最常见的加载小任务的方法。这不仅创建了一个小任务,而且自动将它插入到调试器中。

例子 - 加载一个更具体的小任务:

>>> def func(*args, **kwargs):
...     print "scheduled with", args, "and", kwargs
...
>>> stackless.tasklet(func)(1, 2, 3, string="test")
<stackless.tasklet object at 0x01C58030>
>>> stackless.run()
scheduled with (1, 2, 3) and {'string': 'test'}

1. 小任务、main、current等

有两种需要特别注意的小任务,主小任务(main tasklet)和当前小任务(current tasklet)。

主小任务是固定的,是你的应用程序的开始执行的地方。而成为主小任务的方式是它运行了调度器。

当前小任务是当前正在运行的小任务。如果没有其他小任务在执行,它可能是主小任务。否则,它就是调度器里面可以被执行的小任务的链表的第一个,那就是正在执行的。

例子 - 主小任务是当前小任务吗:

stackless.main == stackless.current

例子 - 当前小任务是主小任务吗:

stackless.current.is_main == 1

例子 - 有多少小任务在被调度:

stackless.runcount

注意:

主小任务也被计算在stackless.runcount内。如果你在主循环中检查在调度器里面有多少个小任务,你需要记住在你创建的小任务之外(之上)还有另外一个小任务。

2. tasklet类

通道(Channel) — 小任务之间的通讯

通道对象是用来在小任务之间进行通讯用的。

在一个小任务里往一个通道发送东西,另一个在这个通道等待接收东西的小任务就会被恢复。如果没有小任务在接收,发送方会被挂起。

在一个小任务里接收一个通道的东西,另一个在这个通道上等待发送的小任务就会被恢复。如果没有发送者,接收方会被挂起。

1. 通道与线程

通道是线程安全的。这意味着在一个线程中执行的小任务可以用通道与另一个线程中执行的小任务进行通讯。这在线程与Stackless一节有详述。

2. channel类

调度器 — 小任务如何运行

Stackless调度小任务的两种主要方法是抢占式调度和协作式调度。如何利用这两种方式来达到应用程序的需求,却有很多方法。

1. 合作式调度(Cooperative scheduling)

最简单的调度器运行方式是合作式。程序员需要知道他们的代码什么时候会发生阻塞,并进行适当的处理。与抢占式调度不同,他们可以准确知道何时会发生阻塞,这就允许他们清晰知道他们的代码与小任务中执行的任何其他东西之间如何协作。

例子 - 在小任务里面运行一个简单函数:

>>> def function(n):
...     for i in range(n):
...         print i+1
...         stackless.schedule()
...
>>> stackless.tasklet(function)(3)
>>> stackless.run()
1
2
3

在上述例子中,定义了一个function函数,然后又定义了一个小任务来调用这个函数,然后运行了调度器。调度器对小任务进行了4次调度。在第四次时,函数退出了,因此小任务也退出了。

在小任务里面执行的代码:

def function(n):
    for i in range(n):
        print i+1
        stackless.schedule()

第一步是执行小任务里面的代码。这里,function函数只是循环了n次,在每一次循环里面打印出这是第几次循环,然后通过运行stackless.schedule()给其他小任务一个调度机会。

创建小任务:

stackless.tasklet(function)(3)

一个小任务指定了要运行的函数,并给出了函数所需的参数(这里是要赋给n的3)。当小任务被第一次调度时,function函数第一次被执行,参数被传递给这个函数。这里创建小任务的操作自动将这个小任务插到调度器里面,因此没有必要再保留小任务的一个引用。

运行调度器:

stackless.run()

接着调度器被运行了。如果没有任何剩余的小任务,它会返回。注意这个小任务会被调度4次。第一次是它的起始运行,会用指定的参数调用小任务里的function函数。第二第三次调度也就是第二和第三次打印,而最后一次调度时,函数退出了,小任务也就退出了。

如果开发者确认,只要程序在运行调度器里面就一定会有小任务(就像上面所演示的),那么应用程序可以用一个stackless.run()调用来驱动。但是情况不总是这么简单,有时候调度器里面可能是空的,那就需要在创建新的小任务之后或者插入老的被阻塞的小任务之后,重复调用stackless.run()。

1.1. 检测不合作的小任务

在实践中,很少会出现一个小任务一直运行而不让其他小任务运行。小任务常常被事件阻塞而自动让出执行权,而不需要显式的去让出来。但是偶尔有预料之外的情况发生,导致程序不能执行到让出执行权的代码或者代码进入了一个死循环。

基于这样的考虑,常常会利用抢占式调度的功能,来检测长时间运行的小任务。办法是设置一个足够大的超时时间,而碰到这个超时的只可能是那些没有让出执行权的代码。

习惯用法 - 检测不合作的小任务:

while 1:
    t = stackless.run(1000000)
    if t is not None:
        t.insert()

        print "*** Uncooperative tasklet", t, "detected ***"
        traceback.print_stack(t.frame)

协作式调度,但是抢占不合作的小任务:

t = stackless.run(1000000)

因为大部分小任务会在执行1000000条指令后让出执行权,而只有那些不让出执行权、不合作的小任务会被中断并返回。

恢复中断的小任务:

if t is not None:
    t.insert()

中断的小任务不会继续留在调度器中。我们不知道这个小任务是做什么的,根据应用程序的需求不让它执行完可能是无法接受的,调用 tasklet.insert()将它重新插回调度器中,放在所有可以运行的小任务的列表末尾,来保证在这个小任务下一次被执行前,其他小任务有机会运行。

如果可以假定这样被中断的小任务都是有问题的,那么可以杀死它。在杀死之前记录下足够的信息(比如调用栈)就更好了。

杀死一个中断的小任务:

if t is not None:
    print "*** Uncooperative tasklet", t, "detected ***"
    traceback.print_stack(t.frame)

    t.kill()

注意

Tasklets that do long running calls outside of Python are not something this mechanism has any insight into. These calls might be doing synchronous IO, complex math module operations that execute in the underlying C library or a range of other things.

1.2. Pumping the scheduler

2. 抢占式调度(Pre-emptive scheduling)

2.1. Running the scheduler for n instructions

3. 异常

3.1. Catching tasklet exceptions

调试与跟踪 — Stackless有什么不同

1. settrace与小任务

线程 — 线程与Stackless

1. 每线程一个调度器

2. 通道是线程安全的

Pickling — 运行中的小任务的序列化

StacklessPython (2010-01-24 11:31:07由125编辑)

ch3n2k.com | Copyright (c) 2004-2020 czk.