私が理解できないことは、ThreadPoolExecutor
はデーモンワーカーを使用しますが、メインスレッドが終了しても実行されるということです。
私はpython3.6.4で最小限の例を提供できます:
import concurrent.futures
import time
def fn():
while True:
time.sleep(5)
print("Hello")
thread_pool = concurrent.futures.ThreadPoolExecutor()
thread_pool.submit(fn)
while True:
time.sleep(1)
print("Wow")
メインスレッドとワーカースレッドはどちらも無限ループです。したがって、KeyboardInterrupt
を使用してメインスレッドを終了すると、プログラム全体も終了することを期待しています。しかし、実際には、ワーカースレッドはデーモンスレッドであっても実行されています。
ThreadPoolExecutor
のソースコードは、ワーカースレッドがデーモンスレッドであることを確認します。
t = threading.Thread(target=_worker,
args=(weakref.ref(self, weakref_cb),
self._work_queue))
t.daemon = True
t.start()
self._threads.add(t)
さらに、デーモンスレッドを手動で作成すると、魅力的に機能します。
from threading import Thread
import time
def fn():
while True:
time.sleep(5)
print("Hello")
thread = Thread(target=fn)
thread.daemon = True
thread.start()
while True:
time.sleep(1)
print("Wow")
だから私はこの奇妙な行動を本当に理解することはできません。
突然...理由がわかりました。より多くのThreadPoolExecutor
のソースコードによると:
# Workers are created as daemon threads. This is done to allow the interpreter
# to exit when there are still idle threads in a ThreadPoolExecutor's thread
# pool (i.e. shutdown() was not called). However, allowing workers to die with
# the interpreter has two undesirable properties:
# - The workers would still be running during interpreter shutdown,
# meaning that they would fail in unpredictable ways.
# - The workers could be killed while evaluating a work item, which could
# be bad if the callable being evaluated has external side-effects e.g.
# writing to a file.
#
# To work around this problem, an exit handler is installed which tells the
# workers to exit when their work queues are empty and then waits until the
# threads finish.
_threads_queues = weakref.WeakKeyDictionary()
_shutdown = False
def _python_exit():
global _shutdown
_shutdown = True
items = list(_threads_queues.items())
for t, q in items:
q.put(None)
for t, q in items:
t.join()
atexit.register(_python_exit)
すべての未完了のワーカーに参加する終了ハンドラーがあります...