There is a simple scheduler built into Stackless Python. It schedules registered tasklets in order, rotating through the list giving each tasklet a turn.
When a tasklet is created, it is automatically appended to the end of this list. However, the scheduler needs to be manually called in order to make it work. The simplest way is to simply run it using the provided method exposed in the stackless module. This removes the main tasklet and schedules each registered tasklet in turn until there are no tasklet remaining in the list.
Example - Running the scheduler:
Heres a more complicated example which demonstrates creating some initial tasklets, shows the order in which they are run and that the scheduler exits when they exit.
Example - Scheduling some tasklets:
def f(i): print i stackless.tasklet(f)(1) stackless.tasklet(f)(2) stackless.run()
Starting the scheduler in the way shown above runs the tasklets in a cooperative manner. It is the responsibility of each tasklet to get itself rescheduled so that other tasklets can have a turn, as they are not interrupted in any way by the scheduler. This can be done by calling the schedule method. The following example shows some potentially long running tasklets yielding to each other.
Example - Scheduling cooperatively:
def InfiniteLoop(i): while 1: stackless.schedule() print "schedule", i stackless.tasklet(InfiniteLoop)(1) stackless.tasklet(InfiniteLoop)(2) stackless.run()
schedule 1 schedule 2 schedule 1 schedule 2 ...
If preemptive scheduling is prefered, then this can be achieved by telling the scheduler to run for only a specific number of instructions.
Example - Running the scheduler until 1000 instructions have been executed:
instructionCount = 1000 stackless.run(instructionCount)
In order to illustrate this with a more practical example, we will just look at the cooperatively scheduled example rewritten to yield implicitly rather than explicitly. In order to do so, we need to manage the schedulers run method.
Example - Scheduling preemptively:
def InfiniteLoop(i): while 1: print "schedule", i stackless.tasklet(InfiniteLoop)(1) stackless.tasklet(InfiniteLoop)(2) # The current main thread of execution counts as one running tasklet, so # we know that if it is the only one remaining, we can exit. while stackless.getruncount() != 1: t = stackless.run(1000) # If we got a tasklet back, it was the one that was interrupted. # we need to reinsert it for rescheduling. if t: t.insert()
schedule 1 ... repeated until 1000 instructions have passed schedule 2 ... repeated until 1000 instructions have passed ...
While preemptive scheduling might be useful in some specific situations, in general the predictibility of cooperative scheduling is invaluable.
The Main Tasklet
Before any tasklets are created, the context that exists initially is called the main tasklet. Here your application probably initialises itself and creates some tasklets in doing so, then does some sort of loop ensuring that it continues until all operation is complete. This is needed because if the main tasklet exits, so does the application, resulting in the implicit death of all existing tasklets.
Unless your use of tasklets is pretty straightforward, where you know that there will always be at least one other in existence, other than the main one. You cannot rely on just calling the run method or looping while calling it to preemptively schedule. Tasklets stalled on channels and others that are blocked do not count towards the running tasklet count and need to be waited for.