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

You want to wrap a general object to make any acess to it thread safe. The ThreadedProxy class accomplishes that.

needs Python 2.2

Python, 90 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
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

Created by Gonçalo Rodrigues on Fri, 25 Oct 2002 (PSF)
Python recipes (4591)
Gonçalo Rodrigues's recipes (9)

Required Modules

Other Information and Tasks