Fake daemons in Python ThreadPoolExecutor

Posted by xlordt on Thu, 30 Apr 2020 07:10:07 +0200

phenomenon

Observe the submit code of ThreadPoolExecutor:

    def _adjust_thread_count(self):
        # When the executor gets lost, the weakref callback will wake up
        # the worker threads.
        def weakref_cb(_, q=self._work_queue):
            q.put(None)
        # TODO(bquinlan): Should avoid creating new threads if there are more
        # idle threads than items in the work queue.
        if len(self._threads) < self._max_workers:
            t = threading.Thread(target=_worker,
                                 args=(weakref.ref(self, weakref_cb),
                                       self._work_queue))
            t.daemon = True   #It's really a daemonic thread
            t.start()
            self._threads.add(t)
            _threads_queues[t] = self._work_queue



The threads started in the thread pool are all daemons, but after the main thread exits, the process does not exit, but waits for the sub thread to end. Later, we found that in order not to cause other bad effects of thread termination caused by sudden interruption, such as half of file writing, etc., atexit exit method was registered in the ThreadPool design. In short, when the calling thread exits, instead of actually exiting, it will call the method registered in ateix, and the exit method of thread pool file is to wait for all threads in the thread pool to exit.

All in all, it's a fascinating design. But the exception or exit that python handle can't do can still exit correctly.

The code is as follows

Code

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




Topics: Programming Python