0001 """Thread module emulating a subset of Java's threading model.""" 0002 0003 import sys as _sys 0004 0005 try: 0006 import thread 0007 except ImportError: 0008 del _sys.modules[__name__] 0009 raise 0010 0011 from time import time as _time, sleep as _sleep 0012 from traceback import format_exc as _format_exc 0013 from collections import deque 0014 0015 # Rename some stuff so "from threading import *" is safe 0016 __all__ = ['activeCount', 'Condition', 'currentThread', 'enumerate', 'Event', 0017 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread', 0018 'Timer', 'setprofile', 'settrace', 'local'] 0019 0020 _start_new_thread = thread.start_new_thread 0021 _allocate_lock = thread.allocate_lock 0022 _get_ident = thread.get_ident 0023 ThreadError = thread.error 0024 del thread 0025 0026 0027 # Debug support (adapted from ihooks.py). 0028 # All the major classes here derive from _Verbose. We force that to 0029 # be a new-style class so that all the major classes here are new-style. 0030 # This helps debugging (type(instance) is more revealing for instances 0031 # of new-style classes). 0032 0033 _VERBOSE = False 0034 0035 if __debug__: 0036 0037 class _Verbose(object): 0038 0039 def __init__(self, verbose=None): 0040 if verbose is None: 0041 verbose = _VERBOSE 0042 self.__verbose = verbose 0043 0044 def _note(self, format, *args): 0045 if self.__verbose: 0046 format = format % args 0047 format = "%s: %s\n" % ( 0048 currentThread().getName(), format) 0049 _sys.stderr.write(format) 0050 0051 else: 0052 # Disable this when using "python -O" 0053 class _Verbose(object): 0054 def __init__(self, verbose=None): 0055 pass 0056 def _note(self, *args): 0057 pass 0058 0059 # Support for profile and trace hooks 0060 0061 _profile_hook = None 0062 _trace_hook = None 0063 0064 def setprofile(func): 0065 global _profile_hook 0066 _profile_hook = func 0067 0068 def settrace(func): 0069 global _trace_hook 0070 _trace_hook = func 0071 0072 # Synchronization classes 0073 0074 Lock = _allocate_lock 0075 0076 def RLock(*args, **kwargs): 0077 return _RLock(*args, **kwargs) 0078 0079 class _RLock(_Verbose): 0080 0081 def __init__(self, verbose=None): 0082 _Verbose.__init__(self, verbose) 0083 self.__block = _allocate_lock() 0084 self.__owner = None 0085 self.__count = 0 0086 0087 def __repr__(self): 0088 return "<%s(%s, %d)>" % ( 0089 self.__class__.__name__, 0090 self.__owner and self.__owner.getName(), 0091 self.__count) 0092 0093 def acquire(self, blocking=1): 0094 me = currentThread() 0095 if self.__owner is me: 0096 self.__count = self.__count + 1 0097 if __debug__: 0098 self._note("%s.acquire(%s): recursive success", self, blocking) 0099 return 1 0100 rc = self.__block.acquire(blocking) 0101 if rc: 0102 self.__owner = me 0103 self.__count = 1 0104 if __debug__: 0105 self._note("%s.acquire(%s): initial succes", self, blocking) 0106 else: 0107 if __debug__: 0108 self._note("%s.acquire(%s): failure", self, blocking) 0109 return rc 0110 0111 def release(self): 0112 me = currentThread() 0113 assert self.__owner is me, "release() of un-acquire()d lock" 0114 self.__count = count = self.__count - 1 0115 if not count: 0116 self.__owner = None 0117 self.__block.release() 0118 if __debug__: 0119 self._note("%s.release(): final release", self) 0120 else: 0121 if __debug__: 0122 self._note("%s.release(): non-final release", self) 0123 0124 # Internal methods used by condition variables 0125 0126 def _acquire_restore(self, (count, owner)): 0127 self.__block.acquire() 0128 self.__count = count 0129 self.__owner = owner 0130 if __debug__: 0131 self._note("%s._acquire_restore()", self) 0132 0133 def _release_save(self): 0134 if __debug__: 0135 self._note("%s._release_save()", self) 0136 count = self.__count 0137 self.__count = 0 0138 owner = self.__owner 0139 self.__owner = None 0140 self.__block.release() 0141 return (count, owner) 0142 0143 def _is_owned(self): 0144 return self.__owner is currentThread() 0145 0146 0147 def Condition(*args, **kwargs): 0148 return _Condition(*args, **kwargs) 0149 0150 class _Condition(_Verbose): 0151 0152 def __init__(self, lock=None, verbose=None): 0153 _Verbose.__init__(self, verbose) 0154 if lock is None: 0155 lock = RLock() 0156 self.__lock = lock 0157 # Export the lock's acquire() and release() methods 0158 self.acquire = lock.acquire 0159 self.release = lock.release 0160 # If the lock defines _release_save() and/or _acquire_restore(), 0161 # these override the default implementations (which just call 0162 # release() and acquire() on the lock). Ditto for _is_owned(). 0163 try: 0164 self._release_save = lock._release_save 0165 except AttributeError: 0166 pass 0167 try: 0168 self._acquire_restore = lock._acquire_restore 0169 except AttributeError: 0170 pass 0171 try: 0172 self._is_owned = lock._is_owned 0173 except AttributeError: 0174 pass 0175 self.__waiters = [] 0176 0177 def __repr__(self): 0178 return "<Condition(%s, %d)>" % (self.__lock, len(self.__waiters)) 0179 0180 def _release_save(self): 0181 self.__lock.release() # No state to save 0182 0183 def _acquire_restore(self, x): 0184 self.__lock.acquire() # Ignore saved state 0185 0186 def _is_owned(self): 0187 # Return True if lock is owned by currentThread. 0188 # This method is called only if __lock doesn't have _is_owned(). 0189 if self.__lock.acquire(0): 0190 self.__lock.release() 0191 return False 0192 else: 0193 return True 0194 0195 def wait(self, timeout=None): 0196 assert self._is_owned(), "wait() of un-acquire()d lock" 0197 waiter = _allocate_lock() 0198 waiter.acquire() 0199 self.__waiters.append(waiter) 0200 saved_state = self._release_save() 0201 try: # restore state no matter what (e.g., KeyboardInterrupt) 0202 if timeout is None: 0203 waiter.acquire() 0204 if __debug__: 0205 self._note("%s.wait(): got it", self) 0206 else: 0207 # Balancing act: We can't afford a pure busy loop, so we 0208 # have to sleep; but if we sleep the whole timeout time, 0209 # we'll be unresponsive. The scheme here sleeps very 0210 # little at first, longer as time goes on, but never longer 0211 # than 20 times per second (or the timeout time remaining). 0212 endtime = _time() + timeout 0213 delay = 0.0005 # 500 us -> initial delay of 1 ms 0214 while True: 0215 gotit = waiter.acquire(0) 0216 if gotit: 0217 break 0218 remaining = endtime - _time() 0219 if remaining <= 0: 0220 break 0221 delay = min(delay * 2, remaining, .05) 0222 _sleep(delay) 0223 if not gotit: 0224 if __debug__: 0225 self._note("%s.wait(%s): timed out", self, timeout) 0226 try: 0227 self.__waiters.remove(waiter) 0228 except ValueError: 0229 pass 0230 else: 0231 if __debug__: 0232 self._note("%s.wait(%s): got it", self, timeout) 0233 finally: 0234 self._acquire_restore(saved_state) 0235 0236 def notify(self, n=1): 0237 assert self._is_owned(), "notify() of un-acquire()d lock" 0238 __waiters = self.__waiters 0239 waiters = __waiters[:n] 0240 if not waiters: 0241 if __debug__: 0242 self._note("%s.notify(): no waiters", self) 0243 return 0244 self._note("%s.notify(): notifying %d waiter%s", self, n, 0245 n!=1 and "s" or "") 0246 for waiter in waiters: 0247 waiter.release() 0248 try: 0249 __waiters.remove(waiter) 0250 except ValueError: 0251 pass 0252 0253 def notifyAll(self): 0254 self.notify(len(self.__waiters)) 0255 0256 0257 def Semaphore(*args, **kwargs): 0258 return _Semaphore(*args, **kwargs) 0259 0260 class _Semaphore(_Verbose): 0261 0262 # After Tim Peters' semaphore class, but not quite the same (no maximum) 0263 0264 def __init__(self, value=1, verbose=None): 0265 assert value >= 0, "Semaphore initial value must be >= 0" 0266 _Verbose.__init__(self, verbose) 0267 self.__cond = Condition(Lock()) 0268 self.__value = value 0269 0270 def acquire(self, blocking=1): 0271 rc = False 0272 self.__cond.acquire() 0273 while self.__value == 0: 0274 if not blocking: 0275 break 0276 if __debug__: 0277 self._note("%s.acquire(%s): blocked waiting, value=%s", 0278 self, blocking, self.__value) 0279 self.__cond.wait() 0280 else: 0281 self.__value = self.__value - 1 0282 if __debug__: 0283 self._note("%s.acquire: success, value=%s", 0284 self, self.__value) 0285 rc = True 0286 self.__cond.release() 0287 return rc 0288 0289 def release(self): 0290 self.__cond.acquire() 0291 self.__value = self.__value + 1 0292 if __debug__: 0293 self._note("%s.release: success, value=%s", 0294 self, self.__value) 0295 self.__cond.notify() 0296 self.__cond.release() 0297 0298 0299 def BoundedSemaphore(*args, **kwargs): 0300 return _BoundedSemaphore(*args, **kwargs) 0301 0302 class _BoundedSemaphore(_Semaphore): 0303 """Semaphore that checks that # releases is <= # acquires""" 0304 def __init__(self, value=1, verbose=None): 0305 _Semaphore.__init__(self, value, verbose) 0306 self._initial_value = value 0307 0308 def release(self): 0309 if self._Semaphore__value >= self._initial_value: 0310 raise ValueError, "Semaphore released too many times" 0311 return _Semaphore.release(self) 0312 0313 0314 def Event(*args, **kwargs): 0315 return _Event(*args, **kwargs) 0316 0317 class _Event(_Verbose): 0318 0319 # After Tim Peters' event class (without is_posted()) 0320 0321 def __init__(self, verbose=None): 0322 _Verbose.__init__(self, verbose) 0323 self.__cond = Condition(Lock()) 0324 self.__flag = False 0325 0326 def isSet(self): 0327 return self.__flag 0328 0329 def set(self): 0330 self.__cond.acquire() 0331 try: 0332 self.__flag = True 0333 self.__cond.notifyAll() 0334 finally: 0335 self.__cond.release() 0336 0337 def clear(self): 0338 self.__cond.acquire() 0339 try: 0340 self.__flag = False 0341 finally: 0342 self.__cond.release() 0343 0344 def wait(self, timeout=None): 0345 self.__cond.acquire() 0346 try: 0347 if not self.__flag: 0348 self.__cond.wait(timeout) 0349 finally: 0350 self.__cond.release() 0351 0352 # Helper to generate new thread names 0353 _counter = 0 0354 def _newname(template="Thread-%d"): 0355 global _counter 0356 _counter = _counter + 1 0357 return template % _counter 0358 0359 # Active thread administration 0360 _active_limbo_lock = _allocate_lock() 0361 _active = {} 0362 _limbo = {} 0363 0364 0365 # Main class for threads 0366 0367 class Thread(_Verbose): 0368 0369 __initialized = False 0370 # Need to store a reference to sys.exc_info for printing 0371 # out exceptions when a thread tries to use a global var. during interp. 0372 # shutdown and thus raises an exception about trying to perform some 0373 # operation on/with a NoneType 0374 __exc_info = _sys.exc_info 0375 0376 def __init__(self, group=None, target=None, name=None, 0377 args=(), kwargs={}, verbose=None): 0378 assert group is None, "group argument must be None for now" 0379 _Verbose.__init__(self, verbose) 0380 self.__target = target 0381 self.__name = str(name or _newname()) 0382 self.__args = args 0383 self.__kwargs = kwargs 0384 self.__daemonic = self._set_daemon() 0385 self.__started = False 0386 self.__stopped = False 0387 self.__block = Condition(Lock()) 0388 self.__initialized = True 0389 # sys.stderr is not stored in the class like 0390 # sys.exc_info since it can be changed between instances 0391 self.__stderr = _sys.stderr 0392 0393 def _set_daemon(self): 0394 # Overridden in _MainThread and _DummyThread 0395 return currentThread().isDaemon() 0396 0397 def __repr__(self): 0398 assert self.__initialized, "Thread.__init__() was not called" 0399 status = "initial" 0400 if self.__started: 0401 status = "started" 0402 if self.__stopped: 0403 status = "stopped" 0404 if self.__daemonic: 0405 status = status + " daemon" 0406 return "<%s(%s, %s)>" % (self.__class__.__name__, self.__name, status) 0407 0408 def start(self): 0409 assert self.__initialized, "Thread.__init__() not called" 0410 assert not self.__started, "thread already started" 0411 if __debug__: 0412 self._note("%s.start(): starting thread", self) 0413 _active_limbo_lock.acquire() 0414 _limbo[self] = self 0415 _active_limbo_lock.release() 0416 _start_new_thread(self.__bootstrap, ()) 0417 self.__started = True 0418 _sleep(0.000001) # 1 usec, to let the thread run (Solaris hack) 0419 0420 def run(self): 0421 if self.__target: 0422 self.__target(*self.__args, **self.__kwargs) 0423 0424 def __bootstrap(self): 0425 try: 0426 self.__started = True 0427 _active_limbo_lock.acquire() 0428 _active[_get_ident()] = self 0429 del _limbo[self] 0430 _active_limbo_lock.release() 0431 if __debug__: 0432 self._note("%s.__bootstrap(): thread started", self) 0433 0434 if _trace_hook: 0435 self._note("%s.__bootstrap(): registering trace hook", self) 0436 _sys.settrace(_trace_hook) 0437 if _profile_hook: 0438 self._note("%s.__bootstrap(): registering profile hook", self) 0439 _sys.setprofile(_profile_hook) 0440 0441 try: 0442 self.run() 0443 except SystemExit: 0444 if __debug__: 0445 self._note("%s.__bootstrap(): raised SystemExit", self) 0446 except: 0447 if __debug__: 0448 self._note("%s.__bootstrap(): unhandled exception", self) 0449 # If sys.stderr is no more (most likely from interpreter 0450 # shutdown) use self.__stderr. Otherwise still use sys (as in 0451 # _sys) in case sys.stderr was redefined since the creation of 0452 # self. 0453 if _sys: 0454 _sys.stderr.write("Exception in thread %s:\n%s\n" % 0455 (self.getName(), _format_exc())) 0456 else: 0457 # Do the best job possible w/o a huge amt. of code to 0458 # approximate a traceback (code ideas from 0459 # Lib/traceback.py) 0460 exc_type, exc_value, exc_tb = self.__exc_info() 0461 try: 0462 print>>self.__stderr, ( 0463 "Exception in thread " + self.getName() + 0464 " (most likely raised during interpreter shutdown):") 0465 print>>self.__stderr, ( 0466 "Traceback (most recent call last):") 0467 while exc_tb: 0468 print>>self.__stderr, ( 0469 ' File "%s", line %s, in %s' % 0470 (exc_tb.tb_frame.f_code.co_filename, 0471 exc_tb.tb_lineno, 0472 exc_tb.tb_frame.f_code.co_name)) 0473 exc_tb = exc_tb.tb_next 0474 print>>self.__stderr, ("%s: %s" % (exc_type, exc_value)) 0475 # Make sure that exc_tb gets deleted since it is a memory 0476 # hog; deleting everything else is just for thoroughness 0477 finally: 0478 del exc_type, exc_value, exc_tb 0479 else: 0480 if __debug__: 0481 self._note("%s.__bootstrap(): normal return", self) 0482 finally: 0483 self.__stop() 0484 try: 0485 self.__delete() 0486 except: 0487 pass 0488 0489 def __stop(self): 0490 self.__block.acquire() 0491 self.__stopped = True 0492 self.__block.notifyAll() 0493 self.__block.release() 0494 0495 def __delete(self): 0496 "Remove current thread from the dict of currently running threads." 0497 0498 # Notes about running with dummy_thread: 0499 # 0500 # Must take care to not raise an exception if dummy_thread is being 0501 # used (and thus this module is being used as an instance of 0502 # dummy_threading). dummy_thread.get_ident() always returns -1 since 0503 # there is only one thread if dummy_thread is being used. Thus 0504 # len(_active) is always <= 1 here, and any Thread instance created 0505 # overwrites the (if any) thread currently registered in _active. 0506 # 0507 # An instance of _MainThread is always created by 'threading'. This 0508 # gets overwritten the instant an instance of Thread is created; both 0509 # threads return -1 from dummy_thread.get_ident() and thus have the 0510 # same key in the dict. So when the _MainThread instance created by 0511 # 'threading' tries to clean itself up when atexit calls this method 0512 # it gets a KeyError if another Thread instance was created. 0513 # 0514 # This all means that KeyError from trying to delete something from 0515 # _active if dummy_threading is being used is a red herring. But 0516 # since it isn't if dummy_threading is *not* being used then don't 0517 # hide the exception. 0518 0519 _active_limbo_lock.acquire() 0520 try: 0521 try: 0522 del _active[_get_ident()] 0523 except KeyError: 0524 if 'dummy_threading' not in _sys.modules: 0525 raise 0526 finally: 0527 _active_limbo_lock.release() 0528 0529 def join(self, timeout=None): 0530 assert self.__initialized, "Thread.__init__() not called" 0531 assert self.__started, "cannot join thread before it is started" 0532 assert self is not currentThread(), "cannot join current thread" 0533 if __debug__: 0534 if not self.__stopped: 0535 self._note("%s.join(): waiting until thread stops", self) 0536 self.__block.acquire() 0537 if timeout is None: 0538 while not self.__stopped: 0539 self.__block.wait() 0540 if __debug__: 0541 self._note("%s.join(): thread stopped", self) 0542 else: 0543 deadline = _time() + timeout 0544 while not self.__stopped: 0545 delay = deadline - _time() 0546 if delay <= 0: 0547 if __debug__: 0548 self._note("%s.join(): timed out", self) 0549 break 0550 self.__block.wait(delay) 0551 else: 0552 if __debug__: 0553 self._note("%s.join(): thread stopped", self) 0554 self.__block.release() 0555 0556 def getName(self): 0557 assert self.__initialized, "Thread.__init__() not called" 0558 return self.__name 0559 0560 def setName(self, name): 0561 assert self.__initialized, "Thread.__init__() not called" 0562 self.__name = str(name) 0563 0564 def isAlive(self): 0565 assert self.__initialized, "Thread.__init__() not called" 0566 return self.__started and not self.__stopped 0567 0568 def isDaemon(self): 0569 assert self.__initialized, "Thread.__init__() not called" 0570 return self.__daemonic 0571 0572 def setDaemon(self, daemonic): 0573 assert self.__initialized, "Thread.__init__() not called" 0574 assert not self.__started, "cannot set daemon status of active thread" 0575 self.__daemonic = daemonic 0576 0577 # The timer class was contributed by Itamar Shtull-Trauring 0578 0579 def Timer(*args, **kwargs): 0580 return _Timer(*args, **kwargs) 0581 0582 class _Timer(Thread): 0583 """Call a function after a specified number of seconds: 0584 0585 t = Timer(30.0, f, args=[], kwargs={}) 0586 t.start() 0587 t.cancel() # stop the timer's action if it's still waiting 0588 """ 0589 0590 def __init__(self, interval, function, args=[], kwargs={}): 0591 Thread.__init__(self) 0592 self.interval = interval 0593 self.function = function 0594 self.args = args 0595 self.kwargs = kwargs 0596 self.finished = Event() 0597 0598 def cancel(self): 0599 """Stop the timer if it hasn't finished yet""" 0600 self.finished.set() 0601 0602 def run(self): 0603 self.finished.wait(self.interval) 0604 if not self.finished.isSet(): 0605 self.function(*self.args, **self.kwargs) 0606 self.finished.set() 0607 0608 # Special thread class to represent the main thread 0609 # This is garbage collected through an exit handler 0610 0611 class _MainThread(Thread): 0612 0613 def __init__(self): 0614 Thread.__init__(self, name="MainThread") 0615 self._Thread__started = True 0616 _active_limbo_lock.acquire() 0617 _active[_get_ident()] = self 0618 _active_limbo_lock.release() 0619 import atexit 0620 atexit.register(self.__exitfunc) 0621 0622 def _set_daemon(self): 0623 return False 0624 0625 def __exitfunc(self): 0626 self._Thread__stop() 0627 t = _pickSomeNonDaemonThread() 0628 if t: 0629 if __debug__: 0630 self._note("%s: waiting for other threads", self) 0631 while t: 0632 t.join() 0633 t = _pickSomeNonDaemonThread() 0634 if __debug__: 0635 self._note("%s: exiting", self) 0636 self._Thread__delete() 0637 0638 def _pickSomeNonDaemonThread(): 0639 for t in enumerate(): 0640 if not t.isDaemon() and t.isAlive(): 0641 return t 0642 return None 0643 0644 0645 # Dummy thread class to represent threads not started here. 0646 # These aren't garbage collected when they die, 0647 # nor can they be waited for. 0648 # Their purpose is to return *something* from currentThread(). 0649 # They are marked as daemon threads so we won't wait for them 0650 # when we exit (conform previous semantics). 0651 0652 class _DummyThread(Thread): 0653 0654 def __init__(self): 0655 Thread.__init__(self, name=_newname("Dummy-%d")) 0656 self._Thread__started = True 0657 _active_limbo_lock.acquire() 0658 _active[_get_ident()] = self 0659 _active_limbo_lock.release() 0660 0661 def _set_daemon(self): 0662 return True 0663 0664 def join(self, timeout=None): 0665 assert False, "cannot join a dummy thread" 0666 0667 0668 # Global API functions 0669 0670 def currentThread(): 0671 try: 0672 return _active[_get_ident()] 0673 except KeyError: 0674 ##print "currentThread(): no current thread for", _get_ident() 0675 return _DummyThread() 0676 0677 def activeCount(): 0678 _active_limbo_lock.acquire() 0679 count = len(_active) + len(_limbo) 0680 _active_limbo_lock.release() 0681 return count 0682 0683 def enumerate(): 0684 _active_limbo_lock.acquire() 0685 active = _active.values() + _limbo.values() 0686 _active_limbo_lock.release() 0687 return active 0688 0689 # Create the main thread object 0690 0691 _MainThread() 0692 0693 # get thread-local implementation, either from the thread 0694 # module, or from the python fallback 0695 0696 try: 0697 from thread import _local as local 0698 except ImportError: 0699 from _threading_local import local 0700 0701 0702 # Self-test code 0703 0704 def _test(): 0705 0706 class BoundedQueue(_Verbose): 0707 0708 def __init__(self, limit): 0709 _Verbose.__init__(self) 0710 self.mon = RLock() 0711 self.rc = Condition(self.mon) 0712 self.wc = Condition(self.mon) 0713 self.limit = limit 0714 self.queue = deque() 0715 0716 def put(self, item): 0717 self.mon.acquire() 0718 while len(self.queue) >= self.limit: 0719 self._note("put(%s): queue full", item) 0720 self.wc.wait() 0721 self.queue.append(item) 0722 self._note("put(%s): appended, length now %d", 0723 item, len(self.queue)) 0724 self.rc.notify() 0725 self.mon.release() 0726 0727 def get(self): 0728 self.mon.acquire() 0729 while not self.queue: 0730 self._note("get(): queue empty") 0731 self.rc.wait() 0732 item = self.queue.popleft() 0733 self._note("get(): got %s, %d left", item, len(self.queue)) 0734 self.wc.notify() 0735 self.mon.release() 0736 return item 0737 0738 class ProducerThread(Thread): 0739 0740 def __init__(self, queue, quota): 0741 Thread.__init__(self, name="Producer") 0742 self.queue = queue 0743 self.quota = quota 0744 0745 def run(self): 0746 from random import random 0747 counter = 0 0748 while counter < self.quota: 0749 counter = counter + 1 0750 self.queue.put("%s.%d" % (self.getName(), counter)) 0751 _sleep(random() * 0.00001) 0752 0753 0754 class ConsumerThread(Thread): 0755 0756 def __init__(self, queue, count): 0757 Thread.__init__(self, name="Consumer") 0758 self.queue = queue 0759 self.count = count 0760 0761 def run(self): 0762 while self.count > 0: 0763 item = self.queue.get() 0764 print item 0765 self.count = self.count - 1 0766 0767 NP = 3 0768 QL = 4 0769 NI = 5 0770 0771 Q = BoundedQueue(QL) 0772 P = [] 0773 for i in range(NP): 0774 t = ProducerThread(Q, NI) 0775 t.setName("Producer-%d" % (i+1)) 0776 P.append(t) 0777 C = ConsumerThread(Q, NI*NP) 0778 for t in P: 0779 t.start() 0780 _sleep(0.000001) 0781 C.start() 0782 for t in P: 0783 t.join() 0784 C.join() 0785 0786 if __name__ == '__main__': 0787 _test() 0788
Generated by PyXR 0.9.4