## Module providing the `Pool` class for managing a process pool## multiprocessing/pool.py## Copyright (c) 2006-2008, R Oudkerk# Licensed to PSF under a Contributor Agreement.#__all__ = ['Pool', 'ThreadPool']## Imports#import collectionsimport itertoolsimport osimport queueimport threadingimport timeimport tracebackimport warningsfrom queue import Empty# If threading is available then ThreadPool should be provided. Therefore# we avoid top-level imports which are liable to fail on some systems.from . import utilfrom . import get_context, TimeoutErrorfrom .connection import wait## Constants representing the state of a pool#INIT = "INIT"RUN = "RUN"CLOSE = "CLOSE"TERMINATE = "TERMINATE"## Miscellaneous#job_counter = itertools.count()def mapstar(args):return list(map(*args))def starmapstar(args):return list(itertools.starmap(args[0], args[1]))## Hack to embed stringification of remote traceback in local traceback#class RemoteTraceback(Exception):def __init__(self, tb):self.tb = tbdef __str__(self):return self.tbclass ExceptionWithTraceback:def __init__(self, exc, tb):tb = traceback.format_exception(type(exc), exc, tb)tb = ''.join(tb)self.exc = excself.tb = '\n"""\n%s"""' % tbdef __reduce__(self):return rebuild_exc, (self.exc, self.tb)def rebuild_exc(exc, tb):exc.__cause__ = RemoteTraceback(tb)return exc## Code run by worker processes#class MaybeEncodingError(Exception):"""Wraps possible unpickleable errors, so they can besafely sent through the socket."""def __init__(self, exc, value):self.exc = repr(exc)self.value = repr(value)super(MaybeEncodingError, self).__init__(self.exc, self.value)def __str__(self):return "Error sending result: '%s'. Reason: '%s'" % (self.value,self.exc)def __repr__(self):return "<%s: %s>" % (self.__class__.__name__, self)def worker(inqueue, outqueue, initializer=None, initargs=(), maxtasks=None,wrap_exception=False):if (maxtasks is not None) and not (isinstance(maxtasks, int)and maxtasks >= 1):raise AssertionError("Maxtasks {!r} is not valid".format(maxtasks))put = outqueue.putget = inqueue.getif hasattr(inqueue, '_writer'):inqueue._writer.close()outqueue._reader.close()if initializer is not None:initializer(*initargs)completed = 0while maxtasks is None or (maxtasks and completed < maxtasks):try:task = get()except (EOFError, OSError):util.debug('worker got EOFError or OSError -- exiting')breakif task is None:util.debug('worker got sentinel -- exiting')breakjob, i, func, args, kwds = tasktry:result = (True, func(*args, **kwds))except Exception as e:if wrap_exception and func is not _helper_reraises_exception:e = ExceptionWithTraceback(e, e.__traceback__)result = (False, e)try:put((job, i, result))except Exception as e:wrapped = MaybeEncodingError(e, result[1])util.debug("Possible encoding error while sending result: %s" % (wrapped))put((job, i, (False, wrapped)))task = job = result = func = args = kwds = Nonecompleted += 1util.debug('worker exiting after %d tasks' % completed)def _helper_reraises_exception(ex):'Pickle-able helper function for use by _guarded_task_generation.'raise ex## Class representing a process pool#class _PoolCache(dict):"""Class that implements a cache for the Pool class that will notifythe pool management threads every time the cache is emptied. Thenotification is done by the use of a queue that is provided wheninstantiating the cache."""def __init__(self, /, *args, notifier=None, **kwds):self.notifier = notifiersuper().__init__(*args, **kwds)def __delitem__(self, item):super().__delitem__(item)# Notify that the cache is empty. This is important because the# pool keeps maintaining workers until the cache gets drained. This# eliminates a race condition in which a task is finished after the# the pool's _handle_workers method has enter another iteration of the# loop. In this situation, the only event that can wake up the pool# is the cache to be emptied (no more tasks available).if not self:self.notifier.put(None)class Pool(object):'''Class which supports an async version of applying functions to arguments.'''_wrap_exception = True@staticmethoddef Process(ctx, *args, **kwds):return ctx.Process(*args, **kwds)def __init__(self, processes=None, initializer=None, initargs=(),maxtasksperchild=None, context=None):# Attributes initialized early to make sure that they exist in# __del__() if __init__() raises an exceptionself._pool = []self._state = INITself._ctx = context or get_context()self._setup_queues()self._taskqueue = queue.SimpleQueue()# The _change_notifier queue exist to wake up self._handle_workers()# when the cache (self._cache) is empty or when there is a change in# the _state variable of the thread that runs _handle_workers.self._change_notifier = self._ctx.SimpleQueue()self._cache = _PoolCache(notifier=self._change_notifier)self._maxtasksperchild = maxtasksperchildself._initializer = initializerself._initargs = initargsif processes is None:processes = os.cpu_count() or 1if processes < 1:raise ValueError("Number of processes must be at least 1")if initializer is not None and not callable(initializer):raise TypeError('initializer must be a callable')self._processes = processestry:self._repopulate_pool()except Exception:for p in self._pool:if p.exitcode is None:p.terminate()for p in self._pool:p.join()raisesentinels = self._get_sentinels()self._worker_handler = threading.Thread(target=Pool._handle_workers,args=(self._cache, self._taskqueue, self._ctx, self.Process,self._processes, self._pool, self._inqueue, self._outqueue,self._initializer, self._initargs, self._maxtasksperchild,self._wrap_exception, sentinels, self._change_notifier))self._worker_handler.daemon = Trueself._worker_handler._state = RUNself._worker_handler.start()self._task_handler = threading.Thread(target=Pool._handle_tasks,args=(self._taskqueue, self._quick_put, self._outqueue,self._pool, self._cache))self._task_handler.daemon = Trueself._task_handler._state = RUNself._task_handler.start()self._result_handler = threading.Thread(target=Pool._handle_results,args=(self._outqueue, self._quick_get, self._cache))self._result_handler.daemon = Trueself._result_handler._state = RUNself._result_handler.start()self._terminate = util.Finalize(self, self._terminate_pool,args=(self._taskqueue, self._inqueue, self._outqueue, self._pool,self._change_notifier, self._worker_handler, self._task_handler,self._result_handler, self._cache),exitpriority=15)self._state = RUN# Copy globals as function locals to make sure that they are available# during Python shutdown when the Pool is destroyed.def __del__(self, _warn=warnings.warn, RUN=RUN):if self._state == RUN:_warn(f"unclosed running multiprocessing pool {self!r}",ResourceWarning, source=self)if getattr(self, '_change_notifier', None) is not None:self._change_notifier.put(None)def __repr__(self):cls = self.__class__return (f'<{cls.__module__}.{cls.__qualname__} 'f'state={self._state} 'f'pool_size={len(self._pool)}>')def _get_sentinels(self):task_queue_sentinels = [self._outqueue._reader]self_notifier_sentinels = [self._change_notifier._reader]return [*task_queue_sentinels, *self_notifier_sentinels]@staticmethoddef _get_worker_sentinels(workers):return [worker.sentinel for worker inworkers if hasattr(worker, "sentinel")]@staticmethoddef _join_exited_workers(pool):"""Cleanup after any worker processes which have exited due to reachingtheir specified lifetime. Returns True if any workers were cleaned up."""cleaned = Falsefor i in reversed(range(len(pool))):worker = pool[i]if worker.exitcode is not None:# worker exitedutil.debug('cleaning up worker %d' % i)worker.join()cleaned = Truedel pool[i]return cleaneddef _repopulate_pool(self):return self._repopulate_pool_static(self._ctx, self.Process,self._processes,self._pool, self._inqueue,self._outqueue, self._initializer,self._initargs,self._maxtasksperchild,self._wrap_exception)@staticmethoddef _repopulate_pool_static(ctx, Process, processes, pool, inqueue,outqueue, initializer, initargs,maxtasksperchild, wrap_exception):"""Bring the number of pool processes up to the specified number,for use after reaping workers which have exited."""for i in range(processes - len(pool)):w = Process(ctx, target=worker,args=(inqueue, outqueue,initializer,initargs, maxtasksperchild,wrap_exception))w.name = w.name.replace('Process', 'PoolWorker')w.daemon = Truew.start()pool.append(w)util.debug('added worker')@staticmethoddef _maintain_pool(ctx, Process, processes, pool, inqueue, outqueue,initializer, initargs, maxtasksperchild,wrap_exception):"""Clean up any exited workers and start replacements for them."""if Pool._join_exited_workers(pool):Pool._repopulate_pool_static(ctx, Process, processes, pool,inqueue, outqueue, initializer,initargs, maxtasksperchild,wrap_exception)def _setup_queues(self):self._inqueue = self._ctx.SimpleQueue()self._outqueue = self._ctx.SimpleQueue()self._quick_put = self._inqueue._writer.sendself._quick_get = self._outqueue._reader.recvdef _check_running(self):if self._state != RUN:raise ValueError("Pool not running")def apply(self, func, args=(), kwds={}):'''Equivalent of `func(*args, **kwds)`.Pool must be running.'''return self.apply_async(func, args, kwds).get()def map(self, func, iterable, chunksize=None):'''Apply `func` to each element in `iterable`, collecting the resultsin a list that is returned.'''return self._map_async(func, iterable, mapstar, chunksize).get()def starmap(self, func, iterable, chunksize=None):'''Like `map()` method but the elements of the `iterable` are expected tobe iterables as well and will be unpacked as arguments. Hence`func` and (a, b) becomes func(a, b).'''return self._map_async(func, iterable, starmapstar, chunksize).get()def starmap_async(self, func, iterable, chunksize=None, callback=None,error_callback=None):'''Asynchronous version of `starmap()` method.'''return self._map_async(func, iterable, starmapstar, chunksize,callback, error_callback)def _guarded_task_generation(self, result_job, func, iterable):'''Provides a generator of tasks for imap and imap_unordered withappropriate handling for iterables which throw exceptions duringiteration.'''try:i = -1for i, x in enumerate(iterable):yield (result_job, i, func, (x,), {})except Exception as e:yield (result_job, i+1, _helper_reraises_exception, (e,), {})def imap(self, func, iterable, chunksize=1):'''Equivalent of `map()` -- can be MUCH slower than `Pool.map()`.'''self._check_running()if chunksize == 1:result = IMapIterator(self)self._taskqueue.put((self._guarded_task_generation(result._job, func, iterable),result._set_length))return resultelse:if chunksize < 1:raise ValueError("Chunksize must be 1+, not {0:n}".format(chunksize))task_batches = Pool._get_tasks(func, iterable, chunksize)result = IMapIterator(self)self._taskqueue.put((self._guarded_task_generation(result._job,mapstar,task_batches),result._set_length))return (item for chunk in result for item in chunk)def imap_unordered(self, func, iterable, chunksize=1):'''Like `imap()` method but ordering of results is arbitrary.'''self._check_running()if chunksize == 1:result = IMapUnorderedIterator(self)self._taskqueue.put((self._guarded_task_generation(result._job, func, iterable),result._set_length))return resultelse:if chunksize < 1:raise ValueError("Chunksize must be 1+, not {0!r}".format(chunksize))task_batches = Pool._get_tasks(func, iterable, chunksize)result = IMapUnorderedIterator(self)self._taskqueue.put((self._guarded_task_generation(result._job,mapstar,task_batches),result._set_length))return (item for chunk in result for item in chunk)def apply_async(self, func, args=(), kwds={}, callback=None,error_callback=None):'''Asynchronous version of `apply()` method.'''self._check_running()result = ApplyResult(self, callback, error_callback)self._taskqueue.put(([(result._job, 0, func, args, kwds)], None))return resultdef map_async(self, func, iterable, chunksize=None, callback=None,error_callback=None):'''Asynchronous version of `map()` method.'''return self._map_async(func, iterable, mapstar, chunksize, callback,error_callback)def _map_async(self, func, iterable, mapper, chunksize=None, callback=None,error_callback=None):'''Helper function to implement map, starmap and their async counterparts.'''self._check_running()if not hasattr(iterable, '__len__'):iterable = list(iterable)if chunksize is None:chunksize, extra = divmod(len(iterable), len(self._pool) * 4)if extra:chunksize += 1if len(iterable) == 0:chunksize = 0task_batches = Pool._get_tasks(func, iterable, chunksize)result = MapResult(self, chunksize, len(iterable), callback,error_callback=error_callback)self._taskqueue.put((self._guarded_task_generation(result._job,mapper,task_batches),None))return result@staticmethoddef _wait_for_updates(sentinels, change_notifier, timeout=None):wait(sentinels, timeout=timeout)while not change_notifier.empty():change_notifier.get()@classmethoddef _handle_workers(cls, cache, taskqueue, ctx, Process, processes,pool, inqueue, outqueue, initializer, initargs,maxtasksperchild, wrap_exception, sentinels,change_notifier):thread = threading.current_thread()# Keep maintaining workers until the cache gets drained, unless the pool# is terminated.while thread._state == RUN or (cache and thread._state != TERMINATE):cls._maintain_pool(ctx, Process, processes, pool, inqueue,outqueue, initializer, initargs,maxtasksperchild, wrap_exception)current_sentinels = [*cls._get_worker_sentinels(pool), *sentinels]cls._wait_for_updates(current_sentinels, change_notifier)# send sentinel to stop workerstaskqueue.put(None)util.debug('worker handler exiting')@staticmethoddef _handle_tasks(taskqueue, put, outqueue, pool, cache):thread = threading.current_thread()for taskseq, set_length in iter(taskqueue.get, None):task = Nonetry:# iterating taskseq cannot failfor task in taskseq:if thread._state != RUN:util.debug('task handler found thread._state != RUN')breaktry:put(task)except Exception as e:job, idx = task[:2]try:cache[job]._set(idx, (False, e))except KeyError:passelse:if set_length:util.debug('doing set_length()')idx = task[1] if task else -1set_length(idx + 1)continuebreakfinally:task = taskseq = job = Noneelse:util.debug('task handler got sentinel')try:# tell result handler to finish when cache is emptyutil.debug('task handler sending sentinel to result handler')outqueue.put(None)# tell workers there is no more workutil.debug('task handler sending sentinel to workers')for p in pool:put(None)except OSError:util.debug('task handler got OSError when sending sentinels')util.debug('task handler exiting')@staticmethoddef _handle_results(outqueue, get, cache):thread = threading.current_thread()while 1:try:task = get()except (OSError, EOFError):util.debug('result handler got EOFError/OSError -- exiting')returnif thread._state != RUN:assert thread._state == TERMINATE, "Thread not in TERMINATE"util.debug('result handler found thread._state=TERMINATE')breakif task is None:util.debug('result handler got sentinel')breakjob, i, obj = tasktry:cache[job]._set(i, obj)except KeyError:passtask = job = obj = Nonewhile cache and thread._state != TERMINATE:try:task = get()except (OSError, EOFError):util.debug('result handler got EOFError/OSError -- exiting')returnif task is None:util.debug('result handler ignoring extra sentinel')continuejob, i, obj = tasktry:cache[job]._set(i, obj)except KeyError:passtask = job = obj = Noneif hasattr(outqueue, '_reader'):util.debug('ensuring that outqueue is not full')# If we don't make room available in outqueue then# attempts to add the sentinel (None) to outqueue may# block. There is guaranteed to be no more than 2 sentinels.try:for i in range(10):if not outqueue._reader.poll():breakget()except (OSError, EOFError):passutil.debug('result handler exiting: len(cache)=%s, thread._state=%s',len(cache), thread._state)@staticmethoddef _get_tasks(func, it, size):it = iter(it)while 1:x = tuple(itertools.islice(it, size))if not x:returnyield (func, x)def __reduce__(self):raise NotImplementedError('pool objects cannot be passed between processes or pickled')def close(self):util.debug('closing pool')if self._state == RUN:self._state = CLOSEself._worker_handler._state = CLOSEself._change_notifier.put(None)def terminate(self):util.debug('terminating pool')self._state = TERMINATEself._worker_handler._state = TERMINATEself._change_notifier.put(None)self._terminate()def join(self):util.debug('joining pool')if self._state == RUN:raise ValueError("Pool is still running")elif self._state not in (CLOSE, TERMINATE):raise ValueError("In unknown state")self._worker_handler.join()self._task_handler.join()self._result_handler.join()for p in self._pool:p.join()@staticmethoddef _help_stuff_finish(inqueue, task_handler, size):# task_handler may be blocked trying to put items on inqueueutil.debug('removing tasks from inqueue until task handler finished')inqueue._rlock.acquire()while task_handler.is_alive() and inqueue._reader.poll():inqueue._reader.recv()time.sleep(0)@classmethoddef _terminate_pool(cls, taskqueue, inqueue, outqueue, pool, change_notifier,worker_handler, task_handler, result_handler, cache):# this is guaranteed to only be called onceutil.debug('finalizing pool')worker_handler._state = TERMINATEtask_handler._state = TERMINATEutil.debug('helping task handler/workers to finish')cls._help_stuff_finish(inqueue, task_handler, len(pool))if (not result_handler.is_alive()) and (len(cache) != 0):raise AssertionError("Cannot have cache with result_hander not alive")result_handler._state = TERMINATEchange_notifier.put(None)outqueue.put(None) # sentinel# We must wait for the worker handler to exit before terminating# workers because we don't want workers to be restarted behind our back.util.debug('joining worker handler')if threading.current_thread() is not worker_handler:worker_handler.join()# Terminate workers which haven't already finished.if pool and hasattr(pool[0], 'terminate'):util.debug('terminating workers')for p in pool:if p.exitcode is None:p.terminate()util.debug('joining task handler')if threading.current_thread() is not task_handler:task_handler.join()util.debug('joining result handler')if threading.current_thread() is not result_handler:result_handler.join()if pool and hasattr(pool[0], 'terminate'):util.debug('joining pool workers')for p in pool:if p.is_alive():# worker has not yet exitedutil.debug('cleaning up worker %d' % p.pid)p.join()def __enter__(self):self._check_running()return selfdef __exit__(self, exc_type, exc_val, exc_tb):self.terminate()## Class whose instances are returned by `Pool.apply_async()`#class ApplyResult(object):def __init__(self, pool, callback, error_callback):self._pool = poolself._event = threading.Event()self._job = next(job_counter)self._cache = pool._cacheself._callback = callbackself._error_callback = error_callbackself._cache[self._job] = selfdef ready(self):return self._event.is_set()def successful(self):if not self.ready():raise ValueError("{0!r} not ready".format(self))return self._successdef wait(self, timeout=None):self._event.wait(timeout)def get(self, timeout=None):self.wait(timeout)if not self.ready():raise TimeoutErrorif self._success:return self._valueelse:raise self._valuedef _set(self, i, obj):self._success, self._value = objif self._callback and self._success:self._callback(self._value)if self._error_callback and not self._success:self._error_callback(self._value)self._event.set()del self._cache[self._job]self._pool = NoneAsyncResult = ApplyResult # create alias -- see #17805## Class whose instances are returned by `Pool.map_async()`#class MapResult(ApplyResult):def __init__(self, pool, chunksize, length, callback, error_callback):ApplyResult.__init__(self, pool, callback,error_callback=error_callback)self._success = Trueself._value = [None] * lengthself._chunksize = chunksizeif chunksize <= 0:self._number_left = 0self._event.set()del self._cache[self._job]else:self._number_left = length//chunksize + bool(length % chunksize)def _set(self, i, success_result):self._number_left -= 1success, result = success_resultif success and self._success:self._value[i*self._chunksize:(i+1)*self._chunksize] = resultif self._number_left == 0:if self._callback:self._callback(self._value)del self._cache[self._job]self._event.set()self._pool = Noneelse:if not success and self._success:# only store first exceptionself._success = Falseself._value = resultif self._number_left == 0:# only consider the result ready once all jobs are doneif self._error_callback:self._error_callback(self._value)del self._cache[self._job]self._event.set()self._pool = None## Class whose instances are returned by `Pool.imap()`#class IMapIterator(object):def __init__(self, pool):self._pool = poolself._cond = threading.Condition(threading.Lock())self._job = next(job_counter)self._cache = pool._cacheself._items = collections.deque()self._index = 0self._length = Noneself._unsorted = {}self._cache[self._job] = selfdef __iter__(self):return selfdef next(self, timeout=None):with self._cond:try:item = self._items.popleft()except IndexError:if self._index == self._length:self._pool = Noneraise StopIteration from Noneself._cond.wait(timeout)try:item = self._items.popleft()except IndexError:if self._index == self._length:self._pool = Noneraise StopIteration from Noneraise TimeoutError from Nonesuccess, value = itemif success:return valueraise value__next__ = next # XXXdef _set(self, i, obj):with self._cond:if self._index == i:self._items.append(obj)self._index += 1while self._index in self._unsorted:obj = self._unsorted.pop(self._index)self._items.append(obj)self._index += 1self._cond.notify()else:self._unsorted[i] = objif self._index == self._length:del self._cache[self._job]self._pool = Nonedef _set_length(self, length):with self._cond:self._length = lengthif self._index == self._length:self._cond.notify()del self._cache[self._job]self._pool = None## Class whose instances are returned by `Pool.imap_unordered()`#class IMapUnorderedIterator(IMapIterator):def _set(self, i, obj):with self._cond:self._items.append(obj)self._index += 1self._cond.notify()if self._index == self._length:del self._cache[self._job]self._pool = None###class ThreadPool(Pool):_wrap_exception = False@staticmethoddef Process(ctx, *args, **kwds):from .dummy import Processreturn Process(*args, **kwds)def __init__(self, processes=None, initializer=None, initargs=()):Pool.__init__(self, processes, initializer, initargs)def _setup_queues(self):self._inqueue = queue.SimpleQueue()self._outqueue = queue.SimpleQueue()self._quick_put = self._inqueue.putself._quick_get = self._outqueue.getdef _get_sentinels(self):return [self._change_notifier._reader]@staticmethoddef _get_worker_sentinels(workers):return []@staticmethoddef _help_stuff_finish(inqueue, task_handler, size):# drain inqueue, and put sentinels at its head to make workers finishtry:while True:inqueue.get(block=False)except queue.Empty:passfor i in range(size):inqueue.put(None)def _wait_for_updates(self, sentinels, change_notifier, timeout):time.sleep(timeout)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。