/Demo/metaclasses/Synch.py

http://unladen-swallow.googlecode.com/ · Python · 256 lines · 180 code · 9 blank · 67 comment · 7 complexity · 043474896daed250aa2bb142184fa233 MD5 · raw file

  1. """Synchronization metaclass.
  2. This metaclass makes it possible to declare synchronized methods.
  3. """
  4. import thread
  5. # First we need to define a reentrant lock.
  6. # This is generally useful and should probably be in a standard Python
  7. # library module. For now, we in-line it.
  8. class Lock:
  9. """Reentrant lock.
  10. This is a mutex-like object which can be acquired by the same
  11. thread more than once. It keeps a reference count of the number
  12. of times it has been acquired by the same thread. Each acquire()
  13. call must be matched by a release() call and only the last
  14. release() call actually releases the lock for acquisition by
  15. another thread.
  16. The implementation uses two locks internally:
  17. __mutex is a short term lock used to protect the instance variables
  18. __wait is the lock for which other threads wait
  19. A thread intending to acquire both locks should acquire __wait
  20. first.
  21. The implementation uses two other instance variables, protected by
  22. locking __mutex:
  23. __tid is the thread ID of the thread that currently has the lock
  24. __count is the number of times the current thread has acquired it
  25. When the lock is released, __tid is None and __count is zero.
  26. """
  27. def __init__(self):
  28. """Constructor. Initialize all instance variables."""
  29. self.__mutex = thread.allocate_lock()
  30. self.__wait = thread.allocate_lock()
  31. self.__tid = None
  32. self.__count = 0
  33. def acquire(self, flag=1):
  34. """Acquire the lock.
  35. If the optional flag argument is false, returns immediately
  36. when it cannot acquire the __wait lock without blocking (it
  37. may still block for a little while in order to acquire the
  38. __mutex lock).
  39. The return value is only relevant when the flag argument is
  40. false; it is 1 if the lock is acquired, 0 if not.
  41. """
  42. self.__mutex.acquire()
  43. try:
  44. if self.__tid == thread.get_ident():
  45. self.__count = self.__count + 1
  46. return 1
  47. finally:
  48. self.__mutex.release()
  49. locked = self.__wait.acquire(flag)
  50. if not flag and not locked:
  51. return 0
  52. try:
  53. self.__mutex.acquire()
  54. assert self.__tid == None
  55. assert self.__count == 0
  56. self.__tid = thread.get_ident()
  57. self.__count = 1
  58. return 1
  59. finally:
  60. self.__mutex.release()
  61. def release(self):
  62. """Release the lock.
  63. If this thread doesn't currently have the lock, an assertion
  64. error is raised.
  65. Only allow another thread to acquire the lock when the count
  66. reaches zero after decrementing it.
  67. """
  68. self.__mutex.acquire()
  69. try:
  70. assert self.__tid == thread.get_ident()
  71. assert self.__count > 0
  72. self.__count = self.__count - 1
  73. if self.__count == 0:
  74. self.__tid = None
  75. self.__wait.release()
  76. finally:
  77. self.__mutex.release()
  78. def _testLock():
  79. done = []
  80. def f2(lock, done=done):
  81. lock.acquire()
  82. print "f2 running in thread %d\n" % thread.get_ident(),
  83. lock.release()
  84. done.append(1)
  85. def f1(lock, f2=f2, done=done):
  86. lock.acquire()
  87. print "f1 running in thread %d\n" % thread.get_ident(),
  88. try:
  89. f2(lock)
  90. finally:
  91. lock.release()
  92. done.append(1)
  93. lock = Lock()
  94. lock.acquire()
  95. f1(lock) # Adds 2 to done
  96. lock.release()
  97. lock.acquire()
  98. thread.start_new_thread(f1, (lock,)) # Adds 2
  99. thread.start_new_thread(f1, (lock, f1)) # Adds 3
  100. thread.start_new_thread(f2, (lock,)) # Adds 1
  101. thread.start_new_thread(f2, (lock,)) # Adds 1
  102. lock.release()
  103. import time
  104. while len(done) < 9:
  105. print len(done)
  106. time.sleep(0.001)
  107. print len(done)
  108. # Now, the Locking metaclass is a piece of cake.
  109. # As an example feature, methods whose name begins with exactly one
  110. # underscore are not synchronized.
  111. from Meta import MetaClass, MetaHelper, MetaMethodWrapper
  112. class LockingMethodWrapper(MetaMethodWrapper):
  113. def __call__(self, *args, **kw):
  114. if self.__name__[:1] == '_' and self.__name__[1:] != '_':
  115. return apply(self.func, (self.inst,) + args, kw)
  116. self.inst.__lock__.acquire()
  117. try:
  118. return apply(self.func, (self.inst,) + args, kw)
  119. finally:
  120. self.inst.__lock__.release()
  121. class LockingHelper(MetaHelper):
  122. __methodwrapper__ = LockingMethodWrapper
  123. def __helperinit__(self, formalclass):
  124. MetaHelper.__helperinit__(self, formalclass)
  125. self.__lock__ = Lock()
  126. class LockingMetaClass(MetaClass):
  127. __helper__ = LockingHelper
  128. Locking = LockingMetaClass('Locking', (), {})
  129. def _test():
  130. # For kicks, take away the Locking base class and see it die
  131. class Buffer(Locking):
  132. def __init__(self, initialsize):
  133. assert initialsize > 0
  134. self.size = initialsize
  135. self.buffer = [None]*self.size
  136. self.first = self.last = 0
  137. def put(self, item):
  138. # Do we need to grow the buffer?
  139. if (self.last+1) % self.size != self.first:
  140. # Insert the new item
  141. self.buffer[self.last] = item
  142. self.last = (self.last+1) % self.size
  143. return
  144. # Double the buffer size
  145. # First normalize it so that first==0 and last==size-1
  146. print "buffer =", self.buffer
  147. print "first = %d, last = %d, size = %d" % (
  148. self.first, self.last, self.size)
  149. if self.first <= self.last:
  150. temp = self.buffer[self.first:self.last]
  151. else:
  152. temp = self.buffer[self.first:] + self.buffer[:self.last]
  153. print "temp =", temp
  154. self.buffer = temp + [None]*(self.size+1)
  155. self.first = 0
  156. self.last = self.size-1
  157. self.size = self.size*2
  158. print "Buffer size doubled to", self.size
  159. print "new buffer =", self.buffer
  160. print "first = %d, last = %d, size = %d" % (
  161. self.first, self.last, self.size)
  162. self.put(item) # Recursive call to test the locking
  163. def get(self):
  164. # Is the buffer empty?
  165. if self.first == self.last:
  166. raise EOFError # Avoid defining a new exception
  167. item = self.buffer[self.first]
  168. self.first = (self.first+1) % self.size
  169. return item
  170. def producer(buffer, wait, n=1000):
  171. import time
  172. i = 0
  173. while i < n:
  174. print "put", i
  175. buffer.put(i)
  176. i = i+1
  177. print "Producer: done producing", n, "items"
  178. wait.release()
  179. def consumer(buffer, wait, n=1000):
  180. import time
  181. i = 0
  182. tout = 0.001
  183. while i < n:
  184. try:
  185. x = buffer.get()
  186. if x != i:
  187. raise AssertionError, \
  188. "get() returned %s, expected %s" % (x, i)
  189. print "got", i
  190. i = i+1
  191. tout = 0.001
  192. except EOFError:
  193. time.sleep(tout)
  194. tout = tout*2
  195. print "Consumer: done consuming", n, "items"
  196. wait.release()
  197. pwait = thread.allocate_lock()
  198. pwait.acquire()
  199. cwait = thread.allocate_lock()
  200. cwait.acquire()
  201. buffer = Buffer(1)
  202. n = 1000
  203. thread.start_new_thread(consumer, (buffer, cwait, n))
  204. thread.start_new_thread(producer, (buffer, pwait, n))
  205. pwait.acquire()
  206. print "Producer done"
  207. cwait.acquire()
  208. print "All done"
  209. print "buffer size ==", len(buffer.buffer)
  210. if __name__ == '__main__':
  211. _testLock()
  212. _test()