views:

82

answers:

2

Let's say I have an object who's class definition looks like:

class Command:
  foo = 5
  def run(self, bar):
    time.sleep(1)
    self.foo = bar
    return self.foo

If this class is instantiated once, but different threads are hitting its run method (via an HTTP request, handled separately) passing in different args, what is the best method to queue them? Can this be done in the class definition itself?

I am using an XML RPC server (separate class). For simplicity's sake, we can say it has one instance of the Command class instantiated as a class variable. When Command.run() is being hit by two separate threads, how can I make sure that one run() method is complete before the next one is started?

I could do something like:

  while self.busy:
    time.sleep(1)
    self.busy = true
    ...
    self.busy = false
    return self.foo

but that, as far as I know, would not give priority to the oldest request.

I realize how redundant this whole exercise sounds, since I could just run the XML-RPC server synchronously. But, to make a long story short, there are multiple Command instantiations and I do not want to block requests for one because another is busy.

I hope this makes more sense.

Thanks.

A: 

That depends on how you plan to consume foo. The simplest is to use Python's queue module to synchronize the delivery of values to a consumer, but that assumes that the consumer wants to receive every value. You might have to be more specific to get a better answer.

Marcelo Cantos
+1  A: 

Here's a relatively simple approach (ignores exceptions, attribute-access, special methods, etc):

import Queue
import threading

def serialize(q):
  """runs a serializer on queue q: put [-1]*4 on q to terminate."""
  while True:
    # get output-queue for result, a callable, its args and kwds
    out_q, tocall, args, kwds = q.get()
    if out_q == -1:
      return
    result = tocall(*args, **kwds)
    out_q.put(result)

class WrapCall(object):
  """Wraps a callable to serialize calls to it."""

  def __init__(self, inq, ouq, tocall):
    self.inq = inq
    self.ouq = ouq
    self.tocall = tocall

  def __call__(self, *a, **k):
    self.inq.put((self.ouq, self.tocall, a, k))
    if self.ouq is None:
      return None
    return self.ouq.get()

class WrapObj(object):
  """Wraps any object to serialize all calls to its methods."""

  def __init__(self, obj):
    self._o = obj
    self._q = Queue.Queue()
    t = threading.Thread(target=serialize, args=(self._q,))
    t.setDaemon(True)
    t.start()
    self.t = t

  def __getattr__(self, n):
    """Wraps methods of self.w into an appropriate WrapCall instance."""
    towrap = getattr(self._o, n)
    if not callable(towrap):
      raise TypeError('Cannot wrap noncallable attribute %r (type: %s)'
                       % (n, type(towrap)))
    q = Queue.Queue()
    return WrapCall(self._q, q, towrap)

  def WrapperWait(self):
    """Return only when self.t has served all pending requests."""
    q = Queue.Queue()
    w = WrapCall(self.__q, q, lambda: None)
    return w()

With this "serializer", you can do

myobj = WrapObj(Command())

and now all calls to myobj's (non-special) methods are serialized in a thread-safe way.

For your specific case, where there's only one method on the object, you could simplify this a bit further, but this is an already-simplified version of something I wrote and use often (supporting attribute getting and setting, special methods, etc, is a tad too complex to be worth it; the complete version does support catching and reraising exceptions raised in the wrapper object's methods, an optimization for calls whose results or exceptions you don't care about, and a few more tweaks, but not serialization of attributes and special methods).

Alex Martelli
Very cool. This is just about what i was hoping for.
Ben
Getting an exception thrown:exceptions.AttributeError: 'WrapCall' object has no attribute 'delegate'Any ideas?Thanks,Ben
Ben
@Ben, sorry, I had renamed an attribute only partially -- it's now named `tocall`, **not** `delegate`, so I just edited it to fix.
Alex Martelli