web-dev-qa-db-fra.com

Les travailleurs de ThreadPoolExecutor ne sont pas vraiment des démons

La chose que je ne peux pas comprendre, c'est que bien que ThreadPoolExecutor utilise des travailleurs de démon, ils continueront à s'exécuter même si la sortie du thread principal.

Je peux fournir un exemple minimal en 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")

Le thread principal et le thread de travail sont des boucles infinies. Donc, si j'utilise KeyboardInterrupt pour terminer le thread principal, je m'attends à ce que tout le programme se termine également. Mais en réalité, le thread de travail est toujours en cours d'exécution, même s'il s'agit d'un thread démon.

Le code source de ThreadPoolExecutor confirme que les threads de travail sont des threads démon:

t = threading.Thread(target=_worker,
                     args=(weakref.ref(self, weakref_cb),
                           self._work_queue))
t.daemon = True
t.start()
self._threads.add(t)

De plus, si je crée manuellement un thread démon, cela fonctionne comme un charme:

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")

Je ne peux donc vraiment pas comprendre ce comportement étrange.

16
Sraw

Soudain ... j'ai trouvé pourquoi. Selon beaucoup plus de code source de 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)

Il y a un gestionnaire de sortie qui rejoindra tous les travailleurs inachevés ...

11
Sraw