Makes sure that only one thread at a time is "inside" the object. This restriction can be lifted for methods whose locking you want to handcode.
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 | def _get_method_names (obj):
from types import *
if type(obj) == InstanceType:
return _get_method_names(obj.__class__)
elif type(obj) == ClassType:
result = []
for name, func in obj.__dict__.items():
if type(func) == FunctionType:
result.append((name, func))
for base in obj.__bases__:
result.extend(_get_method_names(base))
return result
class _SynchronizedMethod:
def __init__ (self, method, obj, lock):
self.__method = method
self.__obj = obj
self.__lock = lock
def __call__ (self, *args, **kwargs):
self.__lock.acquire()
try:
return self.__method(self.__obj, *args, **kwargs)
finally:
self.__lock.release()
class SynchronizedObject:
def __init__ (self, obj, ignore=[], lock=None):
import threading
self.__methods = {}
self.__obj = obj
lock = lock and lock or threading.RLock()
for name, method in _get_method_names(obj):
if not name in ignore:
self.__methods[name] = _SynchronizedMethod(method, obj, lock)
def __getattr__ (self, name):
try:
return self.__methods[name]
except KeyError:
return getattr(self.__obj, name)
if __name__ == '__main__':
import threading
import time
class Dummy:
def foo (self):
print 'hello from foo'
time.sleep(1)
def bar (self):
print 'hello from bar'
def baaz (self):
print 'hello from baaz'
tw = SynchronizedObject(Dummy(), ['baaz'])
threading.Thread(target=tw.foo).start()
time.sleep(.1)
threading.Thread(target=tw.bar).start()
time.sleep(.1)
threading.Thread(target=tw.baaz).start()
#hello from foo
#hello from baaz
#hello from bar
|
Turn to this recipe if you find yourself using the same single-lock locking code in every method - i.e
self.lock.acquire() try: _code_here_ finally: self.lock.release()
It's also practical when you want to postpone worrying about a class' locking until "later". Note however that if you intend to use this code for production purposes you should understand all of it.
Opinions are welcomed.
subtle, interesting bug when wrapping an object that inherits and overrides a method. _get_method_names freely allows duplicate names for inherited and overridden methods, placing the base versions after the overriding ones. But then the loop that builds self.__methods will make the latter (base-class) version 'override' the former (inheriting/overriding!) version. Easy fix, just guard the body of that loop with "if not self.__methods.has_key(name)".
There is a problem when using new style classes. The type checks do not work for new style classess, actually for instance objects of new style classes. Their type is not anymore InstanceType but rather ObjectType. Here follows fixed _get_method_names() function fixed and tested with the Python version 2.2.3:
Might not be 100% pythonic but I apologize as I am C++ convert :-)
Radovan
I thought I know python... ...but I do not understand this line from the SynchronizedObject constructor:
Can someone shed light on what this does?
Short circuit. The line just checks to see if you passed a lock to the function and if you didn't, creates a new one.
This is known as the and/or idiom and acts in a similar way to the ?: operator in C/C++. If lock is None, "lock and lock" will return False, and "new RLock()" will be used. The usage of this idiom is not encouraged however as there are some pitfalls.