I was just very confused by some code that I wrote. I was surprised to discover that:
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
In addition to the explanation in the answers here, it can be helpful to go right to the source. It reaffirms the statement from another answer here that:
.map()
gives results in the order they are submitted, whileFuture
objects with concurrent.futures.as_completed() won't guarantee this ordering, because this is the nature of as_completed()
.map()
is defined in the base class, concurrent.futures._base.Executor:
class Executor(object):
def submit(self, fn, *args, **kwargs):
raise NotImplementedError()
def map(self, fn, *iterables, timeout=None, chunksize=1):
if timeout is not None:
end_time = timeout + time.monotonic()
fs = [self.submit(fn, *args) for args in zip(*iterables)] #
As you mention, there is also .submit()
, which left to be defined in the child classes, namely ProcessPoolExecutor
and ThreadPoolExecutor, and returns a _base.Future
instance that you need to call .result()
on to actually make do anything.
The important lines from .map()
boil down to:
fs = [self.submit(fn, *args) for args in zip(*iterables)]
fs.reverse()
while fs:
yield fs.pop().result()
The .reverse()
plus .pop()
is a means to get the first-submitted result (from iterables
) to be yielded first, the second-submitted result to be yielded second, and so on. The elements of the resulting iterator are not Future
s; they're the actual results themselves.