You want to wrap a general object to make any acess to it thread safe. The ThreadedProxy class accomplishes that.
needs Python 2.2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | """
History:
version 1.2:
- callables and non-callables have distinct classes, now.
- Added a simple factory function TProxy.
"""
#Import modules.
from thread import allocate_lock as Lock
class TObjectProxy(object):
"""The TObjectProxy class wrapper."""
def __init__(self, obj, lock = None):
"""The initializer."""
if lock is None:
lock = Lock()
super(TObjectProxy, self).__init__(obj, lock)
#I'm paranoid => attributes are private.
super(TObjectProxy, self).__setattr__('_TObjectProxy__obj', obj)
super(TObjectProxy, self).__setattr__('_TObjectProxy__lock', lock)
def __repr__(self):
lock = super(TObjectProxy, self).__getattribute__('_TObjectProxy__lock')
obj = super(TObjectProxy, self).__getattribute__('_TObjectProxy__obj')
cls = super(TObjectProxy, self).__getattribute__('__class__')
return "%s<%r, %r>" % (cls.__name__, obj, lock)
#Every attribute get/set is wrapped between locks.
def __getattribute__(self, name):
lock = super(TObjectProxy, self).__getattribute__('_TObjectProxy__lock')
#Marshall everything else to wrapped object
obj = super(TObjectProxy, self).__getattribute__('_TObjectProxy__obj')
lock.acquire()
try:
ret = TProxy(getattr(obj, name), lock)
finally:
lock.release()
return ret
def __setattr__(self, name, value):
obj = super(TObjectProxy, self).__getattribute__('_TObjectProxy__obj')
lock = super(TObjectProxy, self).__getattribute__('_TObjectProxy__lock')
lock.acquire()
try:
setattr(obj, name, value)
finally:
lock.release()
def __delattr__(self, name):
obj = super(TObjectProxy, self).__getattribute__('_TObjectProxy__obj')
lock = super(TObjectProxy, self).__getattribute__('_TObjectProxy__lock')
lock.acquire()
try:
delattr(obj, name)
finally:
lock.release()
class TCallableProxy(TObjectProxy):
"""The TCallableProxy class wrapper."""
def __init__(self, handler, lock = None):
"""The initializer."""
if lock is None:
lock = Lock()
if callable(handler):
super(TCallableProxy, self).__init__(handler, lock)
else:
raise TypeError("Object not callable.", handler)
def __call__(self, *args, **kwargs):
#Get obj and lock => Note how super call uses TObjectProxy.
obj = super(TObjectProxy, self).__getattribute__('_TObjectProxy__obj')
lock = super(TObjectProxy, self).__getattribute__('_TObjectProxy__lock')
lock.acquire()
try:
ret = obj(*args, **kwargs)
finally:
lock.release()
return ret
def TProxy(obj, lock = RLock()):
"""A factory function wrapping an object in a thread-safe wrapper."""
if callable(obj):
return TCallableProxy(obj, lock)
else:
return TObjectProxy(obj, lock)
|
I end up writing a lot of boilerplate code to make acesses to an object thread-safe. So I started musing how could one do this in general way. The strategy is simple: define __getattribute__ and __setattr__ and put a lock around everything that smells of change. This includes wrapping the returned attribute in ThreadedProxy since it can be mutable or it can be a callable changing the object (e.g. think of append for a list).
There are caveats with this solution. Every attribute acess is slowed down. There are locks around everything. It is prolly very easy to enter in a starvation condition. And these are the ones that I can remember off the top of my head. Still, I derived the intelectual satisfaction of pulling this coup off, and in many conditions it can substitute boilerplate boring code.
Have fun! Gonçalo Rodrigues