web-dev-qa-db-ja.com

ThreadPoolExecutorのワーカーは実際にはデーモンではありません

私が理解できないことは、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")

だから私はこの奇妙な行動を本当に理解することはできません。

16
Sraw

突然...理由がわかりました。より多くの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)

すべての未完了のワーカーに参加する終了ハンドラーがあります...

11
Sraw