/Demo/threads/sync.py

http://unladen-swallow.googlecode.com/ · Python · 603 lines · 269 code · 48 blank · 286 comment · 45 complexity · 6e7754a9d3208c4622fa9531fc24b36f MD5 · raw file

  1. # Defines classes that provide synchronization objects. Note that use of
  2. # this module requires that your Python support threads.
  3. #
  4. # condition(lock=None) # a POSIX-like condition-variable object
  5. # barrier(n) # an n-thread barrier
  6. # event() # an event object
  7. # semaphore(n=1) # a semaphore object, with initial count n
  8. # mrsw() # a multiple-reader single-writer lock
  9. #
  10. # CONDITIONS
  11. #
  12. # A condition object is created via
  13. # import this_module
  14. # your_condition_object = this_module.condition(lock=None)
  15. #
  16. # As explained below, a condition object has a lock associated with it,
  17. # used in the protocol to protect condition data. You can specify a
  18. # lock to use in the constructor, else the constructor will allocate
  19. # an anonymous lock for you. Specifying a lock explicitly can be useful
  20. # when more than one condition keys off the same set of shared data.
  21. #
  22. # Methods:
  23. # .acquire()
  24. # acquire the lock associated with the condition
  25. # .release()
  26. # release the lock associated with the condition
  27. # .wait()
  28. # block the thread until such time as some other thread does a
  29. # .signal or .broadcast on the same condition, and release the
  30. # lock associated with the condition. The lock associated with
  31. # the condition MUST be in the acquired state at the time
  32. # .wait is invoked.
  33. # .signal()
  34. # wake up exactly one thread (if any) that previously did a .wait
  35. # on the condition; that thread will awaken with the lock associated
  36. # with the condition in the acquired state. If no threads are
  37. # .wait'ing, this is a nop. If more than one thread is .wait'ing on
  38. # the condition, any of them may be awakened.
  39. # .broadcast()
  40. # wake up all threads (if any) that are .wait'ing on the condition;
  41. # the threads are woken up serially, each with the lock in the
  42. # acquired state, so should .release() as soon as possible. If no
  43. # threads are .wait'ing, this is a nop.
  44. #
  45. # Note that if a thread does a .wait *while* a signal/broadcast is
  46. # in progress, it's guaranteeed to block until a subsequent
  47. # signal/broadcast.
  48. #
  49. # Secret feature: `broadcast' actually takes an integer argument,
  50. # and will wake up exactly that many waiting threads (or the total
  51. # number waiting, if that's less). Use of this is dubious, though,
  52. # and probably won't be supported if this form of condition is
  53. # reimplemented in C.
  54. #
  55. # DIFFERENCES FROM POSIX
  56. #
  57. # + A separate mutex is not needed to guard condition data. Instead, a
  58. # condition object can (must) be .acquire'ed and .release'ed directly.
  59. # This eliminates a common error in using POSIX conditions.
  60. #
  61. # + Because of implementation difficulties, a POSIX `signal' wakes up
  62. # _at least_ one .wait'ing thread. Race conditions make it difficult
  63. # to stop that. This implementation guarantees to wake up only one,
  64. # but you probably shouldn't rely on that.
  65. #
  66. # PROTOCOL
  67. #
  68. # Condition objects are used to block threads until "some condition" is
  69. # true. E.g., a thread may wish to wait until a producer pumps out data
  70. # for it to consume, or a server may wish to wait until someone requests
  71. # its services, or perhaps a whole bunch of threads want to wait until a
  72. # preceding pass over the data is complete. Early models for conditions
  73. # relied on some other thread figuring out when a blocked thread's
  74. # condition was true, and made the other thread responsible both for
  75. # waking up the blocked thread and guaranteeing that it woke up with all
  76. # data in a correct state. This proved to be very delicate in practice,
  77. # and gave conditions a bad name in some circles.
  78. #
  79. # The POSIX model addresses these problems by making a thread responsible
  80. # for ensuring that its own state is correct when it wakes, and relies
  81. # on a rigid protocol to make this easy; so long as you stick to the
  82. # protocol, POSIX conditions are easy to "get right":
  83. #
  84. # A) The thread that's waiting for some arbitrarily-complex condition
  85. # (ACC) to become true does:
  86. #
  87. # condition.acquire()
  88. # while not (code to evaluate the ACC):
  89. # condition.wait()
  90. # # That blocks the thread, *and* releases the lock. When a
  91. # # condition.signal() happens, it will wake up some thread that
  92. # # did a .wait, *and* acquire the lock again before .wait
  93. # # returns.
  94. # #
  95. # # Because the lock is acquired at this point, the state used
  96. # # in evaluating the ACC is frozen, so it's safe to go back &
  97. # # reevaluate the ACC.
  98. #
  99. # # At this point, ACC is true, and the thread has the condition
  100. # # locked.
  101. # # So code here can safely muck with the shared state that
  102. # # went into evaluating the ACC -- if it wants to.
  103. # # When done mucking with the shared state, do
  104. # condition.release()
  105. #
  106. # B) Threads that are mucking with shared state that may affect the
  107. # ACC do:
  108. #
  109. # condition.acquire()
  110. # # muck with shared state
  111. # condition.release()
  112. # if it's possible that ACC is true now:
  113. # condition.signal() # or .broadcast()
  114. #
  115. # Note: You may prefer to put the "if" clause before the release().
  116. # That's fine, but do note that anyone waiting on the signal will
  117. # stay blocked until the release() is done (since acquiring the
  118. # condition is part of what .wait() does before it returns).
  119. #
  120. # TRICK OF THE TRADE
  121. #
  122. # With simpler forms of conditions, it can be impossible to know when
  123. # a thread that's supposed to do a .wait has actually done it. But
  124. # because this form of condition releases a lock as _part_ of doing a
  125. # wait, the state of that lock can be used to guarantee it.
  126. #
  127. # E.g., suppose thread A spawns thread B and later wants to wait for B to
  128. # complete:
  129. #
  130. # In A: In B:
  131. #
  132. # B_done = condition() ... do work ...
  133. # B_done.acquire() B_done.acquire(); B_done.release()
  134. # spawn B B_done.signal()
  135. # ... some time later ... ... and B exits ...
  136. # B_done.wait()
  137. #
  138. # Because B_done was in the acquire'd state at the time B was spawned,
  139. # B's attempt to acquire B_done can't succeed until A has done its
  140. # B_done.wait() (which releases B_done). So B's B_done.signal() is
  141. # guaranteed to be seen by the .wait(). Without the lock trick, B
  142. # may signal before A .waits, and then A would wait forever.
  143. #
  144. # BARRIERS
  145. #
  146. # A barrier object is created via
  147. # import this_module
  148. # your_barrier = this_module.barrier(num_threads)
  149. #
  150. # Methods:
  151. # .enter()
  152. # the thread blocks until num_threads threads in all have done
  153. # .enter(). Then the num_threads threads that .enter'ed resume,
  154. # and the barrier resets to capture the next num_threads threads
  155. # that .enter it.
  156. #
  157. # EVENTS
  158. #
  159. # An event object is created via
  160. # import this_module
  161. # your_event = this_module.event()
  162. #
  163. # An event has two states, `posted' and `cleared'. An event is
  164. # created in the cleared state.
  165. #
  166. # Methods:
  167. #
  168. # .post()
  169. # Put the event in the posted state, and resume all threads
  170. # .wait'ing on the event (if any).
  171. #
  172. # .clear()
  173. # Put the event in the cleared state.
  174. #
  175. # .is_posted()
  176. # Returns 0 if the event is in the cleared state, or 1 if the event
  177. # is in the posted state.
  178. #
  179. # .wait()
  180. # If the event is in the posted state, returns immediately.
  181. # If the event is in the cleared state, blocks the calling thread
  182. # until the event is .post'ed by another thread.
  183. #
  184. # Note that an event, once posted, remains posted until explicitly
  185. # cleared. Relative to conditions, this is both the strength & weakness
  186. # of events. It's a strength because the .post'ing thread doesn't have to
  187. # worry about whether the threads it's trying to communicate with have
  188. # already done a .wait (a condition .signal is seen only by threads that
  189. # do a .wait _prior_ to the .signal; a .signal does not persist). But
  190. # it's a weakness because .clear'ing an event is error-prone: it's easy
  191. # to mistakenly .clear an event before all the threads you intended to
  192. # see the event get around to .wait'ing on it. But so long as you don't
  193. # need to .clear an event, events are easy to use safely.
  194. #
  195. # SEMAPHORES
  196. #
  197. # A semaphore object is created via
  198. # import this_module
  199. # your_semaphore = this_module.semaphore(count=1)
  200. #
  201. # A semaphore has an integer count associated with it. The initial value
  202. # of the count is specified by the optional argument (which defaults to
  203. # 1) passed to the semaphore constructor.
  204. #
  205. # Methods:
  206. #
  207. # .p()
  208. # If the semaphore's count is greater than 0, decrements the count
  209. # by 1 and returns.
  210. # Else if the semaphore's count is 0, blocks the calling thread
  211. # until a subsequent .v() increases the count. When that happens,
  212. # the count will be decremented by 1 and the calling thread resumed.
  213. #
  214. # .v()
  215. # Increments the semaphore's count by 1, and wakes up a thread (if
  216. # any) blocked by a .p(). It's an (detected) error for a .v() to
  217. # increase the semaphore's count to a value larger than the initial
  218. # count.
  219. #
  220. # MULTIPLE-READER SINGLE-WRITER LOCKS
  221. #
  222. # A mrsw lock is created via
  223. # import this_module
  224. # your_mrsw_lock = this_module.mrsw()
  225. #
  226. # This kind of lock is often useful with complex shared data structures.
  227. # The object lets any number of "readers" proceed, so long as no thread
  228. # wishes to "write". When a (one or more) thread declares its intention
  229. # to "write" (e.g., to update a shared structure), all current readers
  230. # are allowed to finish, and then a writer gets exclusive access; all
  231. # other readers & writers are blocked until the current writer completes.
  232. # Finally, if some thread is waiting to write and another is waiting to
  233. # read, the writer takes precedence.
  234. #
  235. # Methods:
  236. #
  237. # .read_in()
  238. # If no thread is writing or waiting to write, returns immediately.
  239. # Else blocks until no thread is writing or waiting to write. So
  240. # long as some thread has completed a .read_in but not a .read_out,
  241. # writers are blocked.
  242. #
  243. # .read_out()
  244. # Use sometime after a .read_in to declare that the thread is done
  245. # reading. When all threads complete reading, a writer can proceed.
  246. #
  247. # .write_in()
  248. # If no thread is writing (has completed a .write_in, but hasn't yet
  249. # done a .write_out) or reading (similarly), returns immediately.
  250. # Else blocks the calling thread, and threads waiting to read, until
  251. # the current writer completes writing or all the current readers
  252. # complete reading; if then more than one thread is waiting to
  253. # write, one of them is allowed to proceed, but which one is not
  254. # specified.
  255. #
  256. # .write_out()
  257. # Use sometime after a .write_in to declare that the thread is done
  258. # writing. Then if some other thread is waiting to write, it's
  259. # allowed to proceed. Else all threads (if any) waiting to read are
  260. # allowed to proceed.
  261. #
  262. # .write_to_read()
  263. # Use instead of a .write_in to declare that the thread is done
  264. # writing but wants to continue reading without other writers
  265. # intervening. If there are other threads waiting to write, they
  266. # are allowed to proceed only if the current thread calls
  267. # .read_out; threads waiting to read are only allowed to proceed
  268. # if there are are no threads waiting to write. (This is a
  269. # weakness of the interface!)
  270. import thread
  271. class condition:
  272. def __init__(self, lock=None):
  273. # the lock actually used by .acquire() and .release()
  274. if lock is None:
  275. self.mutex = thread.allocate_lock()
  276. else:
  277. if hasattr(lock, 'acquire') and \
  278. hasattr(lock, 'release'):
  279. self.mutex = lock
  280. else:
  281. raise TypeError, 'condition constructor requires ' \
  282. 'a lock argument'
  283. # lock used to block threads until a signal
  284. self.checkout = thread.allocate_lock()
  285. self.checkout.acquire()
  286. # internal critical-section lock, & the data it protects
  287. self.idlock = thread.allocate_lock()
  288. self.id = 0
  289. self.waiting = 0 # num waiters subject to current release
  290. self.pending = 0 # num waiters awaiting next signal
  291. self.torelease = 0 # num waiters to release
  292. self.releasing = 0 # 1 iff release is in progress
  293. def acquire(self):
  294. self.mutex.acquire()
  295. def release(self):
  296. self.mutex.release()
  297. def wait(self):
  298. mutex, checkout, idlock = self.mutex, self.checkout, self.idlock
  299. if not mutex.locked():
  300. raise ValueError, \
  301. "condition must be .acquire'd when .wait() invoked"
  302. idlock.acquire()
  303. myid = self.id
  304. self.pending = self.pending + 1
  305. idlock.release()
  306. mutex.release()
  307. while 1:
  308. checkout.acquire(); idlock.acquire()
  309. if myid < self.id:
  310. break
  311. checkout.release(); idlock.release()
  312. self.waiting = self.waiting - 1
  313. self.torelease = self.torelease - 1
  314. if self.torelease:
  315. checkout.release()
  316. else:
  317. self.releasing = 0
  318. if self.waiting == self.pending == 0:
  319. self.id = 0
  320. idlock.release()
  321. mutex.acquire()
  322. def signal(self):
  323. self.broadcast(1)
  324. def broadcast(self, num = -1):
  325. if num < -1:
  326. raise ValueError, '.broadcast called with num %r' % (num,)
  327. if num == 0:
  328. return
  329. self.idlock.acquire()
  330. if self.pending:
  331. self.waiting = self.waiting + self.pending
  332. self.pending = 0
  333. self.id = self.id + 1
  334. if num == -1:
  335. self.torelease = self.waiting
  336. else:
  337. self.torelease = min( self.waiting,
  338. self.torelease + num )
  339. if self.torelease and not self.releasing:
  340. self.releasing = 1
  341. self.checkout.release()
  342. self.idlock.release()
  343. class barrier:
  344. def __init__(self, n):
  345. self.n = n
  346. self.togo = n
  347. self.full = condition()
  348. def enter(self):
  349. full = self.full
  350. full.acquire()
  351. self.togo = self.togo - 1
  352. if self.togo:
  353. full.wait()
  354. else:
  355. self.togo = self.n
  356. full.broadcast()
  357. full.release()
  358. class event:
  359. def __init__(self):
  360. self.state = 0
  361. self.posted = condition()
  362. def post(self):
  363. self.posted.acquire()
  364. self.state = 1
  365. self.posted.broadcast()
  366. self.posted.release()
  367. def clear(self):
  368. self.posted.acquire()
  369. self.state = 0
  370. self.posted.release()
  371. def is_posted(self):
  372. self.posted.acquire()
  373. answer = self.state
  374. self.posted.release()
  375. return answer
  376. def wait(self):
  377. self.posted.acquire()
  378. if not self.state:
  379. self.posted.wait()
  380. self.posted.release()
  381. class semaphore:
  382. def __init__(self, count=1):
  383. if count <= 0:
  384. raise ValueError, 'semaphore count %d; must be >= 1' % count
  385. self.count = count
  386. self.maxcount = count
  387. self.nonzero = condition()
  388. def p(self):
  389. self.nonzero.acquire()
  390. while self.count == 0:
  391. self.nonzero.wait()
  392. self.count = self.count - 1
  393. self.nonzero.release()
  394. def v(self):
  395. self.nonzero.acquire()
  396. if self.count == self.maxcount:
  397. raise ValueError, '.v() tried to raise semaphore count above ' \
  398. 'initial value %r' % self.maxcount
  399. self.count = self.count + 1
  400. self.nonzero.signal()
  401. self.nonzero.release()
  402. class mrsw:
  403. def __init__(self):
  404. # critical-section lock & the data it protects
  405. self.rwOK = thread.allocate_lock()
  406. self.nr = 0 # number readers actively reading (not just waiting)
  407. self.nw = 0 # number writers either waiting to write or writing
  408. self.writing = 0 # 1 iff some thread is writing
  409. # conditions
  410. self.readOK = condition(self.rwOK) # OK to unblock readers
  411. self.writeOK = condition(self.rwOK) # OK to unblock writers
  412. def read_in(self):
  413. self.rwOK.acquire()
  414. while self.nw:
  415. self.readOK.wait()
  416. self.nr = self.nr + 1
  417. self.rwOK.release()
  418. def read_out(self):
  419. self.rwOK.acquire()
  420. if self.nr <= 0:
  421. raise ValueError, \
  422. '.read_out() invoked without an active reader'
  423. self.nr = self.nr - 1
  424. if self.nr == 0:
  425. self.writeOK.signal()
  426. self.rwOK.release()
  427. def write_in(self):
  428. self.rwOK.acquire()
  429. self.nw = self.nw + 1
  430. while self.writing or self.nr:
  431. self.writeOK.wait()
  432. self.writing = 1
  433. self.rwOK.release()
  434. def write_out(self):
  435. self.rwOK.acquire()
  436. if not self.writing:
  437. raise ValueError, \
  438. '.write_out() invoked without an active writer'
  439. self.writing = 0
  440. self.nw = self.nw - 1
  441. if self.nw:
  442. self.writeOK.signal()
  443. else:
  444. self.readOK.broadcast()
  445. self.rwOK.release()
  446. def write_to_read(self):
  447. self.rwOK.acquire()
  448. if not self.writing:
  449. raise ValueError, \
  450. '.write_to_read() invoked without an active writer'
  451. self.writing = 0
  452. self.nw = self.nw - 1
  453. self.nr = self.nr + 1
  454. if not self.nw:
  455. self.readOK.broadcast()
  456. self.rwOK.release()
  457. # The rest of the file is a test case, that runs a number of parallelized
  458. # quicksorts in parallel. If it works, you'll get about 600 lines of
  459. # tracing output, with a line like
  460. # test passed! 209 threads created in all
  461. # as the last line. The content and order of preceding lines will
  462. # vary across runs.
  463. def _new_thread(func, *args):
  464. global TID
  465. tid.acquire(); id = TID = TID+1; tid.release()
  466. io.acquire(); alive.append(id); \
  467. print 'starting thread', id, '--', len(alive), 'alive'; \
  468. io.release()
  469. thread.start_new_thread( func, (id,) + args )
  470. def _qsort(tid, a, l, r, finished):
  471. # sort a[l:r]; post finished when done
  472. io.acquire(); print 'thread', tid, 'qsort', l, r; io.release()
  473. if r-l > 1:
  474. pivot = a[l]
  475. j = l+1 # make a[l:j] <= pivot, and a[j:r] > pivot
  476. for i in range(j, r):
  477. if a[i] <= pivot:
  478. a[j], a[i] = a[i], a[j]
  479. j = j + 1
  480. a[l], a[j-1] = a[j-1], pivot
  481. l_subarray_sorted = event()
  482. r_subarray_sorted = event()
  483. _new_thread(_qsort, a, l, j-1, l_subarray_sorted)
  484. _new_thread(_qsort, a, j, r, r_subarray_sorted)
  485. l_subarray_sorted.wait()
  486. r_subarray_sorted.wait()
  487. io.acquire(); print 'thread', tid, 'qsort done'; \
  488. alive.remove(tid); io.release()
  489. finished.post()
  490. def _randarray(tid, a, finished):
  491. io.acquire(); print 'thread', tid, 'randomizing array'; \
  492. io.release()
  493. for i in range(1, len(a)):
  494. wh.acquire(); j = randint(0,i); wh.release()
  495. a[i], a[j] = a[j], a[i]
  496. io.acquire(); print 'thread', tid, 'randomizing done'; \
  497. alive.remove(tid); io.release()
  498. finished.post()
  499. def _check_sort(a):
  500. if a != range(len(a)):
  501. raise ValueError, ('a not sorted', a)
  502. def _run_one_sort(tid, a, bar, done):
  503. # randomize a, and quicksort it
  504. # for variety, all the threads running this enter a barrier
  505. # at the end, and post `done' after the barrier exits
  506. io.acquire(); print 'thread', tid, 'randomizing', a; \
  507. io.release()
  508. finished = event()
  509. _new_thread(_randarray, a, finished)
  510. finished.wait()
  511. io.acquire(); print 'thread', tid, 'sorting', a; io.release()
  512. finished.clear()
  513. _new_thread(_qsort, a, 0, len(a), finished)
  514. finished.wait()
  515. _check_sort(a)
  516. io.acquire(); print 'thread', tid, 'entering barrier'; \
  517. io.release()
  518. bar.enter()
  519. io.acquire(); print 'thread', tid, 'leaving barrier'; \
  520. io.release()
  521. io.acquire(); alive.remove(tid); io.release()
  522. bar.enter() # make sure they've all removed themselves from alive
  523. ## before 'done' is posted
  524. bar.enter() # just to be cruel
  525. done.post()
  526. def test():
  527. global TID, tid, io, wh, randint, alive
  528. import random
  529. randint = random.randint
  530. TID = 0 # thread ID (1, 2, ...)
  531. tid = thread.allocate_lock() # for changing TID
  532. io = thread.allocate_lock() # for printing, and 'alive'
  533. wh = thread.allocate_lock() # for calls to random
  534. alive = [] # IDs of active threads
  535. NSORTS = 5
  536. arrays = []
  537. for i in range(NSORTS):
  538. arrays.append( range( (i+1)*10 ) )
  539. bar = barrier(NSORTS)
  540. finished = event()
  541. for i in range(NSORTS):
  542. _new_thread(_run_one_sort, arrays[i], bar, finished)
  543. finished.wait()
  544. print 'all threads done, and checking results ...'
  545. if alive:
  546. raise ValueError, ('threads still alive at end', alive)
  547. for i in range(NSORTS):
  548. a = arrays[i]
  549. if len(a) != (i+1)*10:
  550. raise ValueError, ('length of array', i, 'screwed up')
  551. _check_sort(a)
  552. print 'test passed!', TID, 'threads created in all'
  553. if __name__ == '__main__':
  554. test()
  555. # end of module