Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 80 lines
 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.

4 comments

Alex Martelli 22 years, 5 months ago  # | flag

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)".

Radovan Chytracek 20 years, 2 months ago  # | flag

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:

def _get_method_names (obj):
    from types import InstanceType, ObjectType
    from types import ClassType, TypeType
    from types import FunctionType, MethodType

    if  isinstance(obj, ObjectType):
      if isinstance(obj, InstanceType ):
        return _get_method_names(obj.__class__)
      elif isinstance(obj, TypeType)or isinstance(obj, ClassType):
        result = []
        for name, func in obj.__dict__.items():
            if isinstance(func, FunctionType) or isinstance(func, MethodType):
                result.append((name, func))
        for base in obj.__bases__:
            result.extend(_get_method_names(base))
        return result
      else:
        return _get_method_names(obj.__class__)
    else:
      raise TypeError, 'Invalid type'

Might not be 100% pythonic but I apologize as I am C++ convert :-)

Radovan

Mark Meister 19 years, 4 months ago  # | flag

I thought I know python... ...but I do not understand this line from the SynchronizedObject constructor:

lock = lock and lock or threading.RLock()

Can someone shed light on what this does?

Eli Golovinsky 17 years, 2 months ago  # | flag

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.