Friday, 6 June 2008

Stackless notes #1

Creating a tasklet

When you create a tasklet, it is automatically scheduled. This places it at the end of the list of scheduled tasklets, and out of all the tasklets currently waiting to run, it will get its turn last.

Scheduling a new tasklet:

>>> def f():
... pass
...
>>> stackless.tasklet(f)()
There is no need to hold a reference to the tasklet to keep it alive. The scheduler holds a reference to all scheduled tasklets.

Binding a callable to a tasklet:
>>> def f(arg, kwarg=None):
... pass
...
>>> t = stackless.tasklet(f)
>>> t.scheduled
False
>>> t.alive
False
Supplying the parameters for the callable:
>>> t = stackless.tasklet(f)
>>> t(1, kwarg=2)
<stackless.tasklet object="" at="" 0x01b031b0="">
>>> t.alive
True
>>> t.scheduled
True
Running the scheduler

Once started, it will continue to run until it has no scheduled tasklets remaining.
>>> def f():
... print "scheduled once"
... stackless.schedule()
... print "scheduled twice"
...
>>> stackless.tasklet(f)()
<stackless.tasklet object at 0x01B03170>
>>> stackless.run()
scheduled once
scheduled twice
>>>
If a tasklet never exits, the scheduler will continue to run forever. This would happen with the following function:
>>> def f():
... while True:
... stackless.schedule()
...
>>>
Running the scheduler normally

As already shown, the normal way to use Stackless Python, is to create your tasklets and then to run the scheduler until they have all either blocked out of it, or run to completion.

An example function to schedule as a tasklet:
def SomeFunction():
while not shouldExit:
# Do whatever it is this tasklet is supposed to do.
pass
# We need to yield to allow the other scheduled tasklets to run.
stackless.schedule()
Create and schedule the tasklet:
stackless.tasklet(SomeFunction)()
Run the scheduler until there are no remaining scheduled tasklets:
stackless.run()
Using this approach, Stackless is a framework and it drives the running of your application. If your application needs to do something, then it needs to create a tasklet to run alongside the others in the scheduler.

Running the scheduler in a more flexible way

It is often less constraining not to have to shape your program to fit inside a framework. This is not how Stackless was used above, in the normal manner. But it can be done by approaching things a little bit differently.

This is what we do in EVE Online, as Kristján Valur Jónsson described in his presentation at PyCon 2006. The approach is described in the Stackless Python idioms wiki page, as the 'Being Nice' idiom.

In a nutshell, rather than yielding your tasklets to the end of the scheduled tasklet list, you yield them onto a channel and out of the scheduler. This is the 'BeNice' function:
def LoopingFunction():
BeNice()
The scheduler will then exit after each tasklet has been run once. Then when you next get to the point when you can run the scheduler again, you first wake up each tasklet on your yield channel, then run the scheduler.
def MainLoop():
while not exit:
RunNiceTasklets()
stackless.run()
The Stackless 'schedule' method should not be used to yield any more, otherwise you will block on the scheduler running until the tasklets which use it are blocked off the scheduler in some other way, or have exited.

An example implementation of this is provided in the uthread module.
import uthread

def LoopingFunction():
uthread.BeNice()

if __name__ == "__main__":
uthread.Run()
Of course, as is, it is not much use. You need to integrate your logic into the loop within your own version of the 'Run' function. Otherwise, you are not much better off than just using Stackless normally.

No comments:

Post a Comment