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:
The problem is that you transform the result of ThreadPoolExecutor.map
to a list. If you don't do this and instead iterate over the resulting generator directly, the results are still yielded in the original order but the loop continues before all results are ready. You can test this with this example:
import time
import concurrent.futures
e = concurrent.futures.ThreadPoolExecutor(4)
s = range(10)
for i in e.map(time.sleep, s):
print(i)
The reason for the order being kept may be because it's sometimes important that you get results in the same order you give them to map. And results are probably not wrapped in future objects because in some situations it may take just too long to do another map over the list to get all results if you need them. And after all in most cases it's very likely that the next value is ready before the loop processed the first value. This is demonstrated in this example:
import concurrent.futures
executor = concurrent.futures.ThreadPoolExecutor() # Or ProcessPoolExecutor
data = some_huge_list()
results = executor.map(crunch_number, data)
finals = []
for value in results:
finals.append(do_some_stuff(value))
In this example it may be likely that do_some_stuff
takes longer than crunch_number
and if this is really the case it's really not a big loss of performace while you still keep the easy usage of map.
Also since the worker threads(/processes) start processing at the beginning of the list and work their way to the end to the list you submitted the results should be finished in the order they're already yielded by the iterator. Which means in most cases executor.map
is just fine, but in some cases, for example if it doesn't matter in which order you process the values and the function you passed to map
takes very different times to run, the future.as_completed
may be faster.