Почему поток должен сохраняться и препятствовать завершению своего процесса даже после достижения цели?
Хотя в этом вопросе используется дополнительный дочерний процесс, основная проблема полностью укоренена в многопоточности. Следовательно, эту проблему с базисом c можно воспроизвести только с помощью MainProcess
. Ответ, связанный с дополнительным дочерним процессом, можно найти в edit 2 .
Сценарий
Не увидев, чем на самом деле является ваш новый поток в вашем дочернем процессе. при этом вероятный сценарий вашего наблюдаемого поведения заключается в том, что ваш thread-1
запускает еще один thread-2
, о котором вы, возможно, даже не подозреваете. Возможно, он запускается из сторонней библиотеки, в которую вы звоните, или для того, чтобы оставаться внутри stdlib, multiprocessing.Queue.put()
также запускает поток фидера в фоновом режиме.
Этот общий сценарий не является Process
-подклассом -и не связаны с вызовом Process.close()
из самого дочернего процесса (неправильное использование, но без последствий).
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}} * * * * * * * * * * * * * * * *}} в процессе всегда является последним потоком в выходе из процесса присоединение non-daemoni c потоков как часть его _shutdown()
-программы. Это то, что удерживает MainThread
в подвешенном состоянии, в то время как его "поверхностная" работа уже выполнена.
Проблема в потоке, не являющемся демоном, который я запускаю в run () Метод задания. поэтому я могу с уверенностью сказать, что поток не позволяет моему процессу закрыться даже после того, как его MainThread
завершено. но я все еще в замешательстве, потому что целевая функция этого потока, не являющегося демоном, выполнена успешно.
Теперь в этом изображенном сценарии ваша целевая функция для thread-1
может фактически завершиться sh успешно. Однако этот thread-1
запустил еще один thread-2
, который затем делает что-то очень длительное, например, навсегда блокировать в худшем случае.
В: Если сама проблема thread-1
не является проблемой, то почему не зависает, когда вы делаете thread-1
a daemon
?
Это потому, что начальное значение "флага демона унаследовано от потока создания" . Таким образом, создание thread-1
a daemon
делает его потомком thread-2
a daemon
, если только флаг daemon
для thread-2
не установлен явно. При выключении демоны не объединяются, и весь процесс «завершается, когда не осталось живых потоков, не являющихся демонами».
Обратите внимание, что до Python 3.7 , не-daemoni c Темы, созданные Process
, имеют , а не . Это расходящееся поведение для потоков вне MainProcess
было исправлено в bpo-18966 .
Code
Чтобы показать, что этот сценарий уже воспроизводим с более простым В приведенном ниже примере в качестве процесса используется MainProcess
, который не завершается. thread-2
- это Timer
-поток, который начнет и вызовет threading.Barrier(parties=1).wait()
через 10 секунд. Этот .wait()
вызов затем завершится sh немедленно с parties=1
, или будет заблокирован навсегда с parties=2
, потому что в нашей настройке не существует никакой другой стороны, вызывающей .wait()
для этого Barrier
. Это позволяет легко переключать поведение, которое мы хотим воспроизвести.
import threading
def blackbox(parties):
"""Dummy for starting thread we might not know about."""
timer = threading.Timer(10, threading.Barrier(parties=parties).wait) # Thread-2
timer.name = "TimerThread"
timer.start()
def t1_target(parties): # Thread-1
"""Start another thread and exit without joining."""
logger = get_mp_logger()
logger.info(f"ALIVE: {[t.name for t in threading.enumerate()]}")
blackbox(parties)
logger.info(f"ALIVE: {[t.name for t in threading.enumerate()]}")
logger.info("DONE")
if __name__ == '__main__':
import logging
parties = 1
daemon = False
print(f"parties={parties}, daemon={daemon}")
logger = get_mp_logger(logging.INFO)
logger.info(f"ALIVE: {[t.name for t in threading.enumerate()]}")
t = threading.Thread(target=t1_target, args=(parties,), daemon=daemon)
t.start()
t.join()
logger.info(f"ALIVE: {[t.name for t in threading.enumerate()]}")
logger.info("DONE")
Журнал ниже предназначен для parties=1
, поэтому бесконечная блокировка отсутствует, но, поскольку thread-2
не является потоком демона, MainThread
присоединится к нему при выключении. Обратите внимание, что TimerThread
все еще жив после выполнения t1_target
. Здесь основной интерес представляет то, как MainThread
требуется ~ 10 секунд до go с "DONE"
до "process shutting down"
. Эти 10 секунд TimerThread
жив.
parties=1, daemon=False
[18:04:31,977 MainThread <module>] ALIVE: ['MainThread']
[18:04:31,977 Thread-1 t1_target] ALIVE: ['MainThread', 'Thread-1']
[18:04:31,978 Thread-1 t1_target] ALIVE: ['MainThread', 'Thread-1', 'TimerThread']
[18:04:31,978 Thread-1 t1_target] DONE
[18:04:31,978 MainThread <module>] ALIVE: ['MainThread', 'TimerThread']
[18:04:31,978 MainThread <module>] DONE
[18:04:41,978 MainThread info] process shutting down
Process finished with exit code 0
С parties=2
он зависает навсегда на этом этапе, ...
parties=2, daemon=False
[18:05:06,010 MainThread <module>] ALIVE: ['MainThread']
[18:05:06,010 Thread-1 t1_target] ALIVE: ['MainThread', 'Thread-1']
[18:05:06,011 Thread-1 t1_target] ALIVE: ['MainThread', 'Thread-1', 'TimerThread']
[18:05:06,011 Thread-1 t1_target] DONE
[18:05:06,011 MainThread <module>] ALIVE: ['MainThread', 'TimerThread']
[18:05:06,011 MainThread <module>] DONE
... если вы не установите daemon=True
, либо для thread-1
(thread-2
наследование), либо только для thread-2
напрямую.
parties=2, daemon=True
[18:05:35,539 MainThread <module>] ALIVE: ['MainThread']
[18:05:35,539 Thread-1 t1_target] ALIVE: ['MainThread', 'Thread-1']
[18:05:35,539 Thread-1 t1_target] ALIVE: ['MainThread', 'Thread-1', 'TimerThread']
[18:05:35,539 Thread-1 t1_target] DONE
[18:05:35,539 MainThread <module>] ALIVE: ['MainThread', 'TimerThread']
[18:05:35,539 MainThread <module>] DONE
[18:05:35,539 MainThread info] process shutting down
Process finished with exit code 0
Помощник
DEFAULT_MP_FORMAT = \
'[%(asctime)s,%(msecs)03d %(threadName)s %(funcName)s]' \
' %(message)s'
DEFAULT_DATEFORMAT = "%H:%M:%S" # "%Y-%m-%d %H:%M:%S"
def get_mp_logger(level=None, fmt=DEFAULT_MP_FORMAT, datefmt=DEFAULT_DATEFORMAT):
"""
Initialize multiprocessing-logger if needed and return reference.
"""
import multiprocessing.util as util
import logging
logger = util.get_logger()
if not logger.handlers:
logger = util.log_to_stderr(level)
logger.handlers[0].setFormatter(logging.Formatter(fmt, datefmt))
return logger