Reputation: 571
The code below executes as expected by returning the total finish time as almost zero because it doesn't wait for the threads to finish every job.
import concurrent.futures
import time
start = time.perf_counter()
def do_something(seconds):
print(f'Sleeping {seconds} second(s)...')
time.sleep(seconds)
return f'Done Sleeping...{seconds}'
executor= concurrent.futures.ThreadPoolExecutor()
secs = [10, 4, 3, 2, 1]
fs = [executor.submit(do_something, sec) for sec in secs]
finish = time.perf_counter()
print(f'Finished in {round(finish-start, 2)} second(s)')
But with the with
command it does wait:
with concurrent.futures.ThreadPoolExecutor() as executor:
secs = [10, 4, 3, 2, 1]
fs = [executor.submit(do_something, sec) for sec in secs]
Why? What is the reason that with
has this behavior with multithreading?
Upvotes: 6
Views: 1011
Reputation: 50076
Using a concurrent.futures.Executor
in a with
statement is equivalent to calling Executor.shutdown
after using it – causing the executor to wait for all tasks to complete. An Executor
used in a with
guarantees proper shutdown of concurrent tasks even if an error occurs inside the with
block.
Executor.shutdown(wait=True)
Signal the executor that it should free any resources that it is using when the currently pending futures are done executing. Calls to
Executor.submit()
andExecutor.map()
made after shutdown will raiseRuntimeError
.[...]
You can avoid having to call this method explicitly if you use the
with
statement, which will shutdown theExecutor
(waiting as ifExecutor.shutdown()
were called with wait set toTrue
): [...]
Upvotes: 6
Reputation: 77337
concurrent.futures
is not well documented. When you create an executor it must be shutdown to terminate its threads or processes. That code will signal the threads to exit by pushing a None
command to them and then wait for them to complete. In your first case, had you added executor.shutdown()
you'd see the delay. It was there anyway - the program still took 10 seconds to exit.
The executor can be used as a context manager (it as __enter__
and __exit__
methods). When it exits the "with" block, __exit__
is called and it in turn does the shutdown
call for you.
Upvotes: 4