stackless/microthread merge

Will Ware wware at world.std.com
Tue Feb 29 18:47:52 EST 2000


At IPC8, I got the chance to talk to Christian Tismer, the author
of Stackless Python, about the possibility of using it as the basis
for implementing microthreads. (This is desirable because his
hacking of ceval.c was much tidier and better thought-out than mine.)
He suggested that with a stackless build, it should be possible to
implement microthreads entirely in Python.

It is, with one caveat: My earlier implementation looked to the
user like pre-emptive task-switching, which I accomplished by stepping
Python's VM a few opcodes at a time. If I resist the temptation to do
damage in ceval.c, task-switching must be explicit in the user's Python
code, esp. in potentially infinite loops that might block all other
threads. For the time being I regard that as a reasonable price for
leaving all the really hard work to Christian.

For Unix/Linux folks who have been looking for Stackless Python and
finding only Windows versions, I have posted a source tree at
ftp://ftp.std.com/pub/wware/spc152.tgz. It builds on my SunOS box at
work, and appears to work there. I make no other promises for it.

Anyway, on to the microthreads. (The stuff with semaphores is still
badly broken, please ignore it. Thank you.)

====================================================================

"""
Return of the Undead Microthreads
Will Ware, Leap Day 2000

This is an implementation of microthreads built on top of Stackless
Python, which is really the right way to do it. There is one remaining
glitch: task switching is not pre-emptive. Functions must explicitly
yield the processor by calling the "step" method of their threads, and
any infinite loop that doesn't yield will block all the other threads.

In the earlier implementation of microthreads, I dove into ceval.c and
effectively played with "ticker" (actually wastefully invented my own
roughly equivalent counter) and that gave me apparently pre-emptive
task-switching. Stackless Python is so thoroughly inscrutable that I
don't dare to try that now.
"""

import continuation

ZombieError = 'zombie microthread error'
NeverHappensError = 'I believe this never really happens'

class Thread:
    def __init__(self, func, *args, **kw):
        self.func = func
        self.args = args
        self.kw = kw
        self.done = 0
        self.killed = 0
	self.future = self._start

    def _start(self, dummy=None):
        if not self.killed:
            try:
                apply(self.func, (self,) + self.args, self.kw)
                raise ZombieError
            finally:
		if self.killed:
		    raise NeverHappensError
		# Do we really need this??
		# hold = self.func, self.args
		# self.__dict__.clear()
		# self.func, self.args = hold
		self.killed = 1

    def step(self):
        if self.killed:
	    raise ZombieError
	future = self.future
	self.future = continuation.caller()
        future()

class Swarm:
    # a bunch of threads
    def __init__(self):
	self.threadlist = [ ]

    def _one_step(self):
	first = self.threadlist[0]
	self.threadlist = self.threadlist[1:]
	try:
	    first.step()
	    self.threadlist.append(first)
	except ZombieError:
	    pass
    def step(self, n=1):
	while 1:
	    if n <= 0:
		return 1
	    if len(self.threadlist) == 0:
		return 0
	    self._one_step()
	    n = n - 1
    def finish(self):
	while self.step(100):
	    pass

class Blockable:
    def __init__(self, swarm):
	self.swarm = swarm
	self.wakelist = [ ]
    def block(self, thread):
	self.wakelist.append(thread)
	print thread, 'blocked'
	# make the swarm ignore this thread til it wakes again
	raise ZombieError
    def awaken(self, n=1):
	# use n = -1 to awaken everybody who is asleep
	while n != 0 and len(self.wakelist) > 0:
	    z = self.wakelist[0]
	    print z, 'awoken'
	    self.wakelist = self.wakelist[1:]
	    self.swarm.threadlist.append(z)
	    n = n - 1

class Semaphore(Blockable):
    def __init__(self, swarm, n=1):
	Blockable.__init__(self, swarm)
	self.count = n
    def claim(self, thread):
	if self.count < 0:
	    raise 'OUCH!'
	if self.count == 0:
	    self.block(thread)
	self.count = self.count - 1
	thread.step()
    def release(self):
	if self.count == 0:
	    self.awaken()
	self.count = self.count + 1

# # # # # # # # # # # # # #

import random

def factorialTest():
    sw = Swarm()
    for i in range(40):

	def factorial(th, n):
	    prod = 1L
	    for i in range(2, n):
		prod = prod * i
		th.step()
	    print n, prod

	th = Thread(factorial, int(100 * random.random()))
	sw.threadlist.append(th)
    sw.finish()

""" Semaphores obviously need a lot of work, but this is a rough
idea of what to do. Currently, blocked threads terminate prematurely
and I dunno why. """

def semaphoreTest():
    sw = Swarm()
    sem = Semaphore(sw)
    N = 5
    class IdThread(Thread):
	def __init__(self,func,sem,letter):
	    Thread.__init__(self,func,sem)
	    self.letter = letter
	def __repr__(self):
	    return '<Thread %s>' % self.letter
	def _start(self, dummy=None):
	    if not self.killed:
		try:
		    apply(self.func, (self,) + self.args, self.kw)
		    raise ZombieError
		finally:
		    print self, 'terminating'
		    self.killed = 1
    def task(th, sem):
	for i in range(6):
	    print th, i, 'claiming'
	    sem.claim(th)
	    print th, i, 'got it'
	    sem.release()
	    print th, i, 'released it'
    for i in range(N):
	th = IdThread(task, sem, chr(ord('A') + i))
	sw.threadlist.append(th)
    sw.finish()

if __name__ == '__main__':
    factorialTest()

======================================================================
-- 
 - - - - - - - - - - - - - - - - - - - - - - - -
Resistance is futile. Capacitance is efficacious.
Will Ware	email:    wware @ world.std.com



More information about the Python-list mailing list