Hi Guys:

I have started the initial ground work on join-conditions. Join conditions are 
used for various synchronisation problems ranging from barrier synchronisation, 
read-writer problems to high level business logic like "contact 10 suppliers 
and wait for the first four that respond."  I will do the prototyping with 
stackless.py. 

I encountered the "Santa Claus Problem" in a paper called "Jingle Bells: 
Solving the Santa Claus Problem with Polyphonic C#."

http://research.microsoft.com/en-us/um/people/nick/santa.pdf

The problem:

"Santa repeatedly sleeps until wakened by either all of his nine reindeer, back 
from their holidays, or by a group of three of his ten elves. If awakened by 
the reindeer, he harnesses each of them to his sleigh, delivers toys with them 
and finally unharnesses them (allowing them to go off on holiday). If awakened 
by a group of elves, he shows each of the group into his study, consults with 
them on toy R&D and finally shows them each out (allowing them to go back to 
work). Santa should give priority to the reindeer in the case that there is 
both a group of elves and a group of reindeer waiting."

This problem turns out to be tricky to solve. The book "Beautiful Code" 
features a Haskell solution.  The Polyphonic C# solution uses join-conditions. 
Polyphonic C# gets its ideas fro JoCAML. In Polyphonic #C, channels can be 
synchronous or asynchronous. 

I don't see why a Stackless Python solution could not be elegant as well.... 
Unlike implementing select where we essentially copied Plan9 - I cannot simply 
copy Polyphonic C#/JoCAML. 

~

The following is a stab at the problem in Stackless Python with select(). For 
now, I don't include the section for priority*  However I want to use this 
example as a springboard for improving select and introducing join-conditions.


def santa(reindeer, elves):
    reindeerChannels = [ch for name, ch, waitTime in reindeer]
    elvesChannels = [ch for name, ch, waitTime in elves]
    reindeerCases = [ch.receiveCase() for ch in reindeerChannels]
    elvesCases = [ch.receiveCase() for ch in elvesChannels]
    reindeerQueue = []
    elvesQueue = []

    cases = reindeerCases + elvesCases

    while True:
        ch, operation, value = stackless.select(cases)
        print value
        if ch in reindeerChannels: 
           reindeerQueue.append(value)
           if len(reindeerQueue) == 9:
              deliveryToys(reindeerQueue)
              reindeerQueue = []
        elif ch in elvesChannels:
           elvesQueue.append(value)
           if len(elvesQueue) == 3:
              consultWithSanta(elvesQueue)
              elvesQueue = []
        else:
           print "WHAT?"

    stopTwisted()
    print "Twisted is dead"
          

if we use callbacks, the solution may be even more tense but harder to follow 
(I should try this)

Some of the ugliness comes from the need to keep separate data structures for 
reindeer operations (cases) and reindeer channels. I am thinking of doing the 
following to solve the aforementioned problem:

theCase = stackless.select(cases)
if theCase in reindeerCases:
   ....

also it makes it removing an unneeded case much easier.

I am hoping a Stackless Python solution with joins would look like

while True:
   case = stackless.select(joinReindeer, joinElves, endSimulationCh)
   if case == reindeerJoin:
      deliveryToys(reindeerJoin)
   elif case == elvesJoin:
      consultWithSanta(elvesJoin)
   elif case == endSimulationCh:
      sys.exit()


I will soon write in more detail in my blog about how join would be done.
Meanwhile any thoughts?

Cheers,
Andrew

* - (running the programme with priority, there was seldom a case where elves 
and reindeer were ready at the same time - I think this has to do with the 
non-determinism of select).


      
#!/usr/bin/env python
"""
simpleSanta.py
Andrew Francis
October 21th, 2010

The purpose of simpleSanta is as follows:

1) Show a solution to the Santa Claus problem using the select
2) Point out improvements to the select statement
3) Give hints as to why a solution with Join conditions may be easier

for now, we leave out priority to reindeer

"""

import random

import time
import sys

from twisted.internet                                 import reactor
from twisted.internet                                 import defer
from twisted.internet                                 import task
from twisted.python.failure                           import Failure

import stackless

# we can use ETIME and RTIME to control how long reindeers and elves wait
# before asking questions

RTIME = (10,12)
ETIME = (1,12)


def tick(seconds):
    tickCh = stackless.channel()
    reactor.callLater(seconds, tickCh.send, None)
    tickCh.receive()


def startTwisted():
    reactor.run()


def stopTwisted():
    reactor.callLater(1, reactor.stop)
    print "that's all folks"


def worker(ch, name, theRange):
    myChannel = stackless.channel()
    while True:
       waitTime = random.randint(theRange[0], theRange[1])
       tick(waitTime)
       ch.send((name, myChannel))
       answer = myChannel.receive()


"""
a way of telling the tasklets that Santa is done
"""
def acceptChannels(channels, status):
    for name, channel in channels:
        channel.send(status)


def deliveryToys(reindeer):
    print "All the reindeer have arrived - delivering toys"
    acceptChannels(reindeer, True)


def consultWithSanta(elves):
    print "Santa consulting with elves"
    acceptChannels(elves, True)


def santa(reindeer, elves):
    reindeerChannels = [ch for name, ch, waitTime in reindeer]
    elvesChannels = [ch for name, ch, waitTime in elves]
    reindeerCases = [ch.receiveCase() for ch in reindeerChannels]
    elvesCases = [ch.receiveCase() for ch in elvesChannels]
    reindeerQueue = []
    elvesQueue = []

    cases = reindeerCases + elvesCases

    while True:
        ch, operation, value = stackless.select(cases)
        print value
        if ch in reindeerChannels: 
           reindeerQueue.append(value)
           if len(reindeerQueue) == 9:
              deliveryToys(reindeerQueue)
              reindeerQueue = []
        elif ch in elvesChannels:
           elvesQueue.append(value)
           if len(elvesQueue) == 3:
              consultWithSanta(elvesQueue)
              elvesQueue = []
        else:
           print "WHAT?"

    stopTwisted()
    print "Twisted is dead"


def makeWorkers(workers):
    for name, ch, waitTime in workers:
        stackless.tasklet(worker)(ch, name, waitTime)
    return 


if __name__ == "__main__":
   random.seed()
   reindeers = [(reindeer, stackless.channel(), RTIME) for reindeer in \
               ["DANCER", "PRANCER", "VIXEN", "COMET", "CUPID", "DONER", \
                "DASHER", "BLITZEN", "RUDOLPH"]]

   elves = [(elf, stackless.channel(), ETIME) for elf in \
            ['A','B','C','D','E','F','G','H','I','J']]

    
   makeWorkers(reindeers)
   makeWorkers(elves)

   l = task.LoopingCall(stackless.schedule)
   l.start(.001)
   stackless.tasklet(startTwisted)()
   stackless.tasklet(santa)(reindeers, elves)
   stackless.run()
_______________________________________________
Stackless mailing list
[email protected]
http://www.stackless.com/mailman/listinfo/stackless

Reply via email to