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

Synchronizes access to methods of a class with either an instance or class specific lock.

Python, 167 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
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
"""
Defines a decorator, synchronous, that allows calls to methods
of a class to be synchronized using an instance based lock.
tlockname must refer to an instance variable that is some
kind of lock with methods acquire and release.  For thread safety this
would normally be a threading.RLock.

"""

from functools import wraps

def synchronous( tlockname ):
    """A decorator to place an instance based lock around a method """

    def _synched(func):
        @wraps(func)
        def _synchronizer(self,*args, **kwargs):
            tlock = self.__getattribute__( tlockname)
            tlock.acquire()
            try:
                return func(self, *args, **kwargs)
            finally:
                tlock.release()
        return _synchronizer
    return _synched

#-----------------------------------------------------------------
# Usage Examples:

if __name__ == "__main__":
    import threading

    class MyClass(object):
        mm = 0
        def __init__(self):
            self.mylock = threading.RLock()
            self.myList = []
            self.n = 0

        @synchronous('mylock')
        def addToMyList(self, item):
            self.n = self.n+1
            self.__class__.mm = self.mm+1
            time.sleep(0.01)
            self.myList.append(str(self.n)+str(self.mm)+item)
            self.n = self.n-1
            self.__class__.mm = self.mm-1

        @synchronous('mylock')
        def popListItem(self):
            return self.myList.pop(0)

#Within any instance of MyClass, the methods addToMyList and
#popListItem cannot be invoked at the same time (via different threads).

    class Class2(object):
        classlock = threading.RLock()
        mm = 0

        def __init__(self):
            self.myList = []
            self.n = 0

        @synchronous('classlock')
        def addToMyList(self, item):
            self.n = self.n+1
            self.__class__.mm = self.mm+1
            time.sleep(0.01)
            self.myList.append(str(self.n)+str(self.mm)+item)
            self.n = self.n-1
            self.__class__.mm = self.mm-1

        @synchronous('classlock')
        def popListItem(self):
            return self.myList.pop(0)

# In the above example all instances use the same class based lock.

# The decorator may be used to synchronize methods of an existing class
# by subclassing the class, with the __init__ method creating the lock
# and the methods that are to be synchronized redeclared, as in the following.

    #Existing unsynchronized class---
    class SomeBase(object):
        mm = 0

        def __init__(self, arg1):
            self.arg1 = arg1
            self.myList = []
            self.n = 0

        def addToMyList(self, item):
            self.n = self.n+1
            self.__class__.mm = self.mm+1
            time.sleep(0.01)
            self.myList.append(str(self.n)+str(self.mm)+item)
            self.n = self.n-1
            self.__class__.mm = self.mm-1

        def popListItem(self):
            return self.myList.pop(0)

    #Derived synchronous class---
    class SafeSomeBase(SomeBase):
        def __init__(self, arg1):
            SomeBase.__init__(self,arg1)
            self.instlock = threading.RLock()

        addToMyList = synchronous('instlock')(SomeBase.addToMyList)
        popListItem = synchronous('instlock')(SomeBase.popListItem)


    plock = threading.RLock()

    import time
    def threadProc( name, baseobj ):
        popped=[]
        tsleep = .05 + ((ord(name[0])+ord(name[3]))%11)*.01
        for j in range(5):
            baseobj.addToMyList( name+str(j) )
            time.sleep(tsleep)
            try:
                popped.append(baseobj.popListItem())
            except Exception, ex:
                print ex
            time.sleep(.13);

        time.sleep(1.0)
        plock.acquire()
        print name, popped
        plock.release()

    def synchTest(thrdnames, synchlist):
        threads=[]
        ix = 0
        for n in thrdnames:
           synched = synchlist[ix]
           ix = (ix+1) % len(synchlist)
           thrd = threading.Thread(target=threadProc, name = n, args=(n, synched))
           threads.append( thrd)

        for thread in threads:
            thread.start()

#        for av in range(22):
#            lcpy = []
#            lcpy.extend(synched.myList)
#            print lcpy
#            time.sleep(.056)
            
        for thread in threads:
            thread.join()

        ok = raw_input("Next? ")

    thrdnames = "Karl Nora Jean Lena Bart Dawn Owen Dave".split()
    synched = [MyClass(), MyClass()]
    synchTest(thrdnames, synched)

    synched = [Class2(), Class2()]
    synchTest(thrdnames, synched)
    
    synched = [SafeSomeBase('alph'),SafeSomeBase('beta')]
    synchTest(thrdnames, synched)
    
    synched = [SomeBase('alph'), SomeBase('beta')]
    synchTest(thrdnames, synched)

This module is very similar to the decorator in ActiveState recipe 465057. The difference being the decorator in that recipe requires a specific lock instance, whereas this one allows the lock to exist as an instance member or a class member, with the attribute name provided to the decorator.

The test examples show the effects of various locking, whether the lock is instance based or class based, or in the last case when no lock is used.

When the addToMyList method it calls it first increments instance and class based variables, waits a bit, and appends a string containing the value of those variables to its list. If instance based locking is present, the first digit should always be 1. If class based locking is present, the first and second digits should always be 1. When no locking is present, the values can change.

The last example shows how to derive a threadsafe subclass from an unprotected base.