stackless python 101

Download Stackless Python 101

If you can't read please download the document

Upload: guest162fd90

Post on 16-Apr-2017

3.836 views

Category:

Technology


0 download

TRANSCRIPT

Stackless Python 101

An advanced level presentation in 20 minutes

Kiwi PyCon9th November 2009

Richard [email protected]

www.stackless.com

- I am a one of two maintainers of Stackless Python.- The other is Kristjan Jonsson.

- This presentation was listed for advanced users.- I will allude to high level concepts like threading.- Experienced programmers should know them.- No time to explain them myself.

What is Stackless?

An extended version of Python.

Backwards compatible.

Stable.

Forked from Python:- Hosted in same VCS as Python.- Thanks PSF.- But branched and forked.

Backwards compatible:- Any Python code/most extensions should run in it.- Exception: PyQT (has low level changes)

Provides a minimal stable set of functionality:- Up to the user to do a lot of legwork.

Obsolete name:- Was: Continuations / heap usage Python rewrite.- Now: Stack copy & paste unobtrusive changes.

Who uses Stackless

CCP Games

Who the hell knows?

CCP chose Python because of Stackless. They wanted coroutines and microthreading, and Stackless came along at the right time. Might have chosen a custom implementation of Javascript otherwise.

CCP is the most public user, and pretty much the only supporter in terms of resources and manpower.

Most usage is unadvertised. We never hear about it. At PyCon 2009, quite a few people came up to the booth I was manning and mentioned how they or their company use it.

What Stackless provides

Lightweight threading.

More readable code.

Persistence of running code.

Lightweight threading:- A form of green threading.- A normal thread might have 1M stack each.- Stackless' tasklets have none.- Very fast to switch between.

More readable code:- The power of Stackless is in its blocking.- Allows boilerplate to be avoided.- Will go into more detail.

Code persistence:- Can pickle a running tasklet.- Pickle is cross platform.- So pickled tasklets are too (Mac PC).

Lightweight threading?

- Not uncommon to see people saying, the GIL will get in our way, Stackless is a solution that could help. This is wrong.- Not an out of the box solution for your scaling needs.- The Python interpreter runs in a thread.- Stackless runs within that thread.- It allocates time to each lightweight thread from that threads allocated CPU time.

The shared thread

Tasklet 1Tasklet 2

Have SLP, running the scheduler.Tasklet 1 is running within the scheduler.Current function does something to tell the scheduler to pause it and run the next tasklet in line.

So the scheduler just puts the tasklet to the side.It effectively clips it out of place where it is, and as it is (the exact point of execution).Then it takes the previously clipped and paused Tasklet 2 from where it was stored, pastes it back into place.And resumes it running at the exact point of execution it was stopped

Who decides when to stop a tasklet? The tasklet usually asks for it. But the scheduler might do it.

More readable code

Taskletdef Function_1():Function_2()

def Function_2():Current_Function()

def Current_Function():# whatever...

So when this tasklet blocks for some reason:- It just gets set aside where it is.- Function 1 does not need to do anything.- Function 2 does not need to do anything.- You can just write Python code that calls functions and if you get blocked, you don't need to do any special handling to allow the blocking to happen.

Reasons to block

Allow other tasklets to run.

Reawaken when event occurs.

Reawaken when time has passed.

The most straightforward reason is to allow other scheduled tasklets to have some CPU time. The scheduler provides a method for you to call to tell it to take care of this. However, my experience is that you almost never end up doing this.

More common is to block your execution until some event occurs. The user needs to build the methods to support this themselves from the primitives Stackless provides.

And also common is blocking your execution until an approximate amount of time has passed. In fact, this is what you tend to do when you would otherwise yield to other tasklets. Again the user needs to build the methods to support this.

Example: Python sockets

def Function_1(self):self.socket = socket(AF_INET, SOCK_STREAM)...self.Function_2()

def Blocking_Function(self):return self.socket.recv(1000)

This is the kind of blocking you do not want. Any method which blocks the whole interpreter until some time in the future, also stops the scheduler from running.

And Python sockets are a good example of this.

When the 'recv' method is called, the entire Stackless process is blocked until the socket is closed or data is received.

This means that Python sockets or anything that works in a similar manner, or any standard library modules that rely on them, are unusable within Stackless.

stacklesssocket.py

Third party library.Monkey patches in place of Python sockets

import stacklesssocketstacklesssocket.install()

However, if socket was written so that it did not block the whole interpreter, but instead only blocked the calling tasklet when its methods were called.. then it and any modules that rely on it would just work with Stackless.

And this what the third party 'stacklesssocket.py' module does.

I wrote it some time ago. It wraps the 'asyncore' module, and monkey patches in a replacement socket module built on top of it.

Example: Stackless sockets

def Function_1(self):self.socket = socket(AF_INET, SOCK_STREAM)...self.Function_2()

def Blocking_Function(self):return self.socket.recv(1000)

So, where this code was problematic before because it blocked the whole interpreter.

Now, with 'stacklesssocket' installed, the current tasklet invoking the socket code blocks, and the scheduler runs any other scheduled tasklets until data arrives and the blocked tasklet is reawakened and rescheduled to deal with it.

Where's the readability?

No callbacks.No boilerplate.Just a function call.

do_something_i_dont_care_if_you_block()

So, where's the readability.

Using stackless' primitives, you can block a tasklet until a callback occurs, then resume it with the result.

Any functions higher up in the tasklet's call stack don't have to do anything. Don't require any boilerplate to handle the fact they are blocked.

The simplest way to add concurrency is to hide it behind a synchronous call. And this is what Stackless allows.

Comparison: generators

Generators block to the calling function.Not the scheduler.

http://www.dabeaz.com/coroutines

Now generators are a useful addition to Python. But they've suffered from bloat, and now support a range of additional functionality. Like being able to send values into a generator for it to use when resumed. And a range of similar turd polishing.

However, generators only block to the calling function. So if you have functions that call functions and you want the call stack to be treated like a tasklet, then you need boilerplate to chain the calls in a manner where this can work.

I'm not going to go into much more detail, you can check out the presentation at the given link if you want more detail.

Using Stackless

Tasklets.

The scheduler.

Channels.

There are three core tools Stackless adds to Python.

Tasklets. Wrap the logic which to be run in a microthread.

The scheduler. Created tasklets are inserted within it and given their turn at being run.

Channels. Used for communication between tasklets and in general constructing manual ways of blocking tasklets.

The scheduler

stackless.run(...)

In order to use Stackless, you need to use the scheduler.

Pretty straightforward, just call stackless.run and it will go away and schedule tasklets in the fashion you desire.

If you're familiar with threading. You may know of cooperative and pre-emptive scheduling, both of these are supported by the Stackless scheduler.

Tasklets are scheduled in a round-robin manner. This means all the tasklets in the scheduler are in a circular list, and the scheduler moves through them one after the other, so they all get a fair turn.

Scheduling cooperatively

Tasklets choose when to block

stackless.schedule()channels...

Run the scheduler until it is empty.

stackless.run()

If you are going to use Stackless, this is the approach I would recommend you take.

When you have code running within a tasklet, you know where it will block because you will be explicitly writing the statements that make it block.

stackless.schedule tells the scheduler to block the current tasklet (the one that called it) and to put it at the end of the list of tasklets to be scheduled. Then the next tasklet in the list is unblocked and continues running.

To do cooperative scheduling in Stackless, you just run the scheduler and tasklets are expected to yield.

Scheduling preemptively

while 1:
interruptedTasklet = stackless.run(100)interruptedTasklet.insert()# Do other stuff.

Personally, I consider pre-emptive scheduling to be more effort than it is worth. But Stackless supports it.

To schedule preemptively, you pass an integer to stackless.run which represents the number of Python VM opcodes to run.

The scheduler will exit returning the interrupted tasklet, which will no longer be in the scheduler. So you'll want to add it at the end of the list of scheduled tasklets again, to ensure it continues running when you next run the scheduler.

The rest of the presentation is going to assume cooperative scheduling, preemptive schedulers are on their own.

Tasklets

stackless.tasklet(print_args)(1, 2, x=test)

def print_args(*args, **kwargs):print 'args', argsprint 'kwargs', kwargs

(1, 2){x: 'test'}

This is the standard tasklet creation idiom.

First a tasklet instance is created by giving a function to run within the tasklet when it is scheduled.

Next the tasklet instance is itself called with the arguments to pass to the function when the tasklet is first run.

It is important to note that this second step implicitly completes setting up the tasklet, and automatically inserts the tasklet into the scheduler to be run in turn.

- Don't need to keep a reference to the tasklet.

Tasklets and threads

Thread-boundtaskletAs I have described, Stackless runs within the Python interpreter thread. The scheduler switches in and out tasklets giving them a turn to run.

Any tasklet created within the thread runs within that thread.

But what is not commonly known, is that any Python thread you create can run a scheduler, and that scheduler can have its own tasklets. You cannot insert tasklets created in one thread into another threads scheduler. Tasklets are unbreakably linked to the threads they are created in.

In practice, no-one really knows of this, or tries scheduling in other threads, so no big deal.

Tasklet yielding

def print_args(*args, **kwargs):
while 1: print 'args', argsprint 'kwargs', kwargsstackless.schedule()

Okay, so before I mentioned that tasklets could ask the scheduler to block them and set them aside until the other scheduled tasklets had a turn to run.

'stackless.schedule' is the method to call within a tasklet to do this.

In this case, using the 'print_args' method supplied earlier and rewriting it... it would infinitely loop repeatedly printing out what was passed in as arguments. But in a way, where between iterations other tasklets did other stuff.

Channels

Used for:Tasklet blocking.

Tasklet communication.

Channels are the primitive stackless provides to block tasklets in arbitrary ways.

Channel operations

Sending:channel.send(value)

Receiving:value = channel.receive()

A channel is bidirectional.

It has a queue for tasklets that are blocked on it, but they will all be senders or all be receivers.

If you receive on a channel that has blocked senders, then what happens is that the value sent by the first blocked sender is returned to you, and the previously blocked sender is rescheduled to continue running.

And vice versa for sending on a channel that has blocked receivers.

If no-one is blocked in the corresponding manner for the operation you are doing, then you will be blocked.

One blocking tasklet

def send_value(channel, value):channel.send(value)

channel = stackless.channel()

stackless.tasklet(send_value)(channel, 1)

stackless.run()

Step through this describing what is happening.

Multiple blocking tasklets

def send_value(channel, value):channel.send(value)

channel = stackless.channel()

stackless.tasklet(send_value)(channel, 1)stackless.tasklet(send_value)(channel, 2)stackless.tasklet(send_value)(channel, 3)

stackless.run()

Step through this describing what is happening, describing the difference between this and the previous slide.

Emptying the channel

def empty_channel(channel):print 'tasklets waiting:', channel.balance

while channel.balance > 0:value = channel.receive()print 'received', value

stackless.tasklet(empty_channel)(channel)

stackless.run()

This continues on from the last slide and uses the same variables (that is 'channel' with the three blocked senders).

Step through it describing what is happening.

Idiom: Blocking out of the scheduler

rapture_channel = stackless.channel()

def wait_for_rapture():rapture_channel.receive()

def call_me_when_rapture_occurs():while rapture_channel.balance < 0:rapture_channel.send(None)

Now you have some idea of how channels work, you might be able to see the most common and simplest idiom for using channels in Stackless.

Just using channels for blocking. You define a method a tasklet is to call when it is to be blocked until some known point in time. And when that known point in time arrives, you reawaken all the blocked tasklets.

The values sent and received are ignored.

What I have not covered

Persistence of running code

Inter-thread channel usage.

C.

Okay, so quickly covering the things I do not have time to go into detail about.

Pickling blocked tasklets, unpickling them and resuming their execution. Pickling is platform independent, so you can of course send the tasklets from a PC to a Mac. Or to another Stackless interpreter running on another core.

Inter-thread channel usage. As seen earlier, each Python thread being run within the same Python interpreter can have its own scheduler with its own tasklets. Channels can actually be used by different threads tasklets, to communicate.

C frames. Blocking from C.

What you need to know

You're on your own buddy... (almost)

Stackless doesn't provide you with the kitchen sink.It isn't usable out of the box.If you want what its primitives have to offer, then you're going to have to build the tools and resources you need yourself.That includes doing what stacklesssocket does for Python socket, for any other form of blocking IO you might want to use.But that's okay. Users have different needs.

The mailing list is the best place to go.There's no formal documentation.Suck it up... it's worth it.

Questions

Stackless website:http://www.stackless.com

Stackless examples:http://code.google.com/p/stacklessexamples