14
14
#
15
15
16
16
import collections
17
+ import functools
17
18
import itertools
18
19
import os
19
20
import queue
25
26
26
27
# If threading is available then ThreadPool should be provided. Therefore
27
28
# we avoid top-level imports which are liable to fail on some systems.
28
- from . import util
29
- from . import get_context , TimeoutError
29
+ from . import TimeoutError , get_context , util
30
30
from .connection import wait
31
31
32
32
#
@@ -395,32 +395,20 @@ def _guarded_task_generation(self, result_job, func, iterable):
395
395
yield (result_job , i + 1 , _helper_reraises_exception , (e ,), {})
396
396
397
397
def _guarded_task_generation_lazy (self , result_job , func , iterable ,
398
- lazy_task_gen_helper ):
399
- ''' Provides a generator of tasks for imap and imap_unordered with
398
+ backpressure_sema ):
399
+ """ Provides a generator of tasks for imap and imap_unordered with
400
400
appropriate handling for iterables which throw exceptions during
401
- iteration.'''
402
- if not lazy_task_gen_helper .feature_enabled :
403
- yield from self ._guarded_task_generation (result_job , func , iterable )
404
- return
405
-
401
+ iteration."""
406
402
try :
407
403
i = - 1
408
404
enumerated_iter = iter (enumerate (iterable ))
409
- thread = threading .current_thread ()
410
- max_generated_tasks = self ._processes + lazy_task_gen_helper .buffersize
411
-
412
- while thread ._state == RUN :
413
- with lazy_task_gen_helper .iterator_cond :
414
- if lazy_task_gen_helper .not_finished_tasks >= max_generated_tasks :
415
- continue # wait for some task to be (picked up and) finished
416
-
405
+ while True :
406
+ backpressure_sema .acquire ()
417
407
try :
418
- i , x = enumerated_iter . __next__ ( )
408
+ i , x = next ( enumerated_iter )
419
409
except StopIteration :
420
410
break
421
-
422
411
yield (result_job , i , func , (x ,), {})
423
- lazy_task_gen_helper .tasks_generated += 1
424
412
425
413
except Exception as e :
426
414
yield (result_job , i + 1 , _helper_reraises_exception , (e ,), {})
@@ -430,31 +418,32 @@ def imap(self, func, iterable, chunksize=1, buffersize=None):
430
418
Equivalent of `map()` -- can be MUCH slower than `Pool.map()`.
431
419
'''
432
420
self ._check_running ()
421
+ if chunksize < 1 :
422
+ raise ValueError ("Chunksize must be 1+, not {0:n}" .format (chunksize ))
423
+
424
+ result = IMapIterator (self , buffersize )
425
+
426
+ if result ._backpressure_sema is None :
427
+ task_generation = self ._guarded_task_generation
428
+ else :
429
+ task_generation = functools .partial (
430
+ self ._guarded_task_generation_lazy ,
431
+ backpressure_sema = result ._backpressure_sema ,
432
+ )
433
+
433
434
if chunksize == 1 :
434
- result = IMapIterator (self , buffersize )
435
435
self ._taskqueue .put (
436
436
(
437
- self ._guarded_task_generation_lazy (result ._job ,
438
- func ,
439
- iterable ,
440
- result ._lazy_task_gen_helper ),
437
+ task_generation (result ._job , func , iterable ),
441
438
result ._set_length ,
442
439
)
443
440
)
444
441
return result
445
442
else :
446
- if chunksize < 1 :
447
- raise ValueError (
448
- "Chunksize must be 1+, not {0:n}" .format (
449
- chunksize ))
450
443
task_batches = Pool ._get_tasks (func , iterable , chunksize )
451
- result = IMapIterator (self , buffersize )
452
444
self ._taskqueue .put (
453
445
(
454
- self ._guarded_task_generation_lazy (result ._job ,
455
- mapstar ,
456
- task_batches ,
457
- result ._lazy_task_gen_helper ),
446
+ task_generation (result ._job , mapstar , task_batches ),
458
447
result ._set_length ,
459
448
)
460
449
)
@@ -465,30 +454,34 @@ def imap_unordered(self, func, iterable, chunksize=1, buffersize=None):
465
454
Like `imap()` method but ordering of results is arbitrary.
466
455
'''
467
456
self ._check_running ()
457
+ if chunksize < 1 :
458
+ raise ValueError (
459
+ "Chunksize must be 1+, not {0!r}" .format (chunksize )
460
+ )
461
+
462
+ result = IMapUnorderedIterator (self , buffersize )
463
+
464
+ if result ._backpressure_sema is None :
465
+ task_generation = self ._guarded_task_generation
466
+ else :
467
+ task_generation = functools .partial (
468
+ self ._guarded_task_generation_lazy ,
469
+ backpressure_sema = result ._backpressure_sema ,
470
+ )
471
+
468
472
if chunksize == 1 :
469
- result = IMapUnorderedIterator (self , buffersize )
470
473
self ._taskqueue .put (
471
474
(
472
- self ._guarded_task_generation_lazy (result ._job ,
473
- func ,
474
- iterable ,
475
- result ._lazy_task_gen_helper ),
475
+ task_generation (result ._job , func , iterable ),
476
476
result ._set_length ,
477
477
)
478
478
)
479
479
return result
480
480
else :
481
- if chunksize < 1 :
482
- raise ValueError (
483
- "Chunksize must be 1+, not {0!r}" .format (chunksize ))
484
481
task_batches = Pool ._get_tasks (func , iterable , chunksize )
485
- result = IMapUnorderedIterator (self , buffersize )
486
482
self ._taskqueue .put (
487
483
(
488
- self ._guarded_task_generation_lazy (result ._job ,
489
- mapstar ,
490
- task_batches ,
491
- result ._lazy_task_gen_helper ),
484
+ task_generation (result ._job , mapstar , task_batches ),
492
485
result ._set_length ,
493
486
)
494
487
)
@@ -889,7 +882,13 @@ def __init__(self, pool, buffersize):
889
882
self ._length = None
890
883
self ._unsorted = {}
891
884
self ._cache [self ._job ] = self
892
- self ._lazy_task_gen_helper = _LazyTaskGenHelper (buffersize , self ._cond )
885
+
886
+ if buffersize is None :
887
+ self ._backpressure_sema = None
888
+ else :
889
+ self ._backpressure_sema = threading .Semaphore (
890
+ value = self ._pool ._processes + buffersize
891
+ )
893
892
894
893
def __iter__ (self ):
895
894
return self
@@ -910,7 +909,9 @@ def next(self, timeout=None):
910
909
self ._pool = None
911
910
raise StopIteration from None
912
911
raise TimeoutError from None
913
- self ._lazy_task_gen_helper .tasks_finished += 1
912
+
913
+ if self ._backpressure_sema :
914
+ self ._backpressure_sema .release ()
914
915
915
916
success , value = item
916
917
if success :
@@ -959,22 +960,6 @@ def _set(self, i, obj):
959
960
del self ._cache [self ._job ]
960
961
self ._pool = None
961
962
962
- #
963
- # Class to store stats for lazy task generation and share them
964
- # between the main thread and `_guarded_task_generation()` thread.
965
- #
966
- class _LazyTaskGenHelper (object ):
967
- def __init__ (self , buffersize , iterator_cond ):
968
- self .feature_enabled = buffersize is not None
969
- self .buffersize = buffersize
970
- self .tasks_generated = 0
971
- self .tasks_finished = 0
972
- self .iterator_cond = iterator_cond
973
-
974
- @property
975
- def not_finished_tasks (self ):
976
- return self .tasks_generated - self .tasks_finished
977
-
978
963
#
979
964
#
980
965
#
0 commit comments