【问题标题】:Python multiprocessing daemon vs non-daemon vs mainPython 多进程守护进程 vs 非守护进程 vs 主进程
【发布时间】:2015-02-20 04:56:11
【问题描述】:

我正在学习 Python 中的多处理,同时我发现守护进程和非守护进程之间的这种奇怪行为与主进程有关。 我的代码:

import multiprocessing
import time

def worker(name,num):
    print name, 'Starting'
    time.sleep(num)
    print name, 'Exiting'

if __name__ == '__main__':
    print 'main starting'
    p1=multiprocessing.Process(target=worker, args=("p1",7,))
    p2=multiprocessing.Process(target=worker, args=("p2",5,))
    p2.daemon=True
    p1.start()
    p2.start()
    time.sleep(4)
    print 'main exiting'

我得到的输出是:

main starting
p1 Starting
p2 Starting
main exiting
p1 Exiting

预期输出:

main starting
p1 Starting
p2 Starting
main exiting
p2 Exiting
p1 Exiting

经过几次搜索,我找到了this answer,并在我的代码中插入了以下行。

logger = multiprocessing.log_to_stderr(logging.INFO)

我得到的输出是,

main starting
[INFO/p1] child process calling self.run()
p1 Starting
[INFO/p2] child process calling self.run()
p2 Starting
main exiting
[INFO/MainProcess] process shutting down
[INFO/MainProcess] calling terminate() for daemon p2
[INFO/MainProcess] calling join() for process p2
[INFO/MainProcess] calling join() for process p1
p1 Exiting
[INFO/p1] process shutting down
[INFO/p1] process exiting with exitcode 0

据我了解

  • 当主进程退出时,它会终止其所有子守护进程。 (可以在这里看到)
  • 主进程无法在其所有子非守护进程退出之前退出。

但是在这里,为什么主进程在 p1 退出之前试图关闭?

  • p1 启动并休眠 7 秒。
  • p2 启动并休眠 5 秒。
  • 主进程休眠 4 秒。
  • 主进程在 4 秒后唤醒并等待 p1 退出。
  • p2 在 5 秒后唤醒并退出。
  • p1 在 7 秒后唤醒并退出。
  • 主进程退出。

对于上述计划来说,上述时间线不是正常的吗?

谁能解释一下这里发生了什么以及为什么?

编辑

在代码末尾添加p1.join() 行后,我得到以下输出:

main starting
[INFO/Process-1] child process calling self.run()
p1 Starting
[INFO/Process-2] child process calling self.run()
p2 Starting
main exiting
p2 Exiting
[INFO/Process-2] process shutting down
[INFO/Process-2] process exiting with exitcode 0
p1 Exiting
[INFO/Process-1] process shutting down
[INFO/Process-1] process exiting with exitcode 0
[INFO/MainProcess] process shutting down

【问题讨论】:

    标签: python multiprocessing


    【解决方案1】:

    当你看到

    [INFO/MainProcess] calling join() for process p1
    

    这意味着主进程还没有退出——它在正在关闭的过程中,但当然不会在join返回之前完成关闭......这将发生仅在连接过程完成后。

    所以时间线确实如您所愿——但由于joinp1main 进程的最后一个想法,您在输出或日志中看不到这一点从中。 (main 已经执行了所有终止触发的操作,但作为一个进程,它在此之前仍然存在)。

    要验证,请在运行此程序时从另一个终端使用(在 Unixy 系统上)ps(可能会有稍长的延迟来帮助您检查):您将永远不会看到一个 Python 进程用完这个复杂的 - 那里将是两个(主要和 p1)直到结束。

    【讨论】:

    • 我不担心p1joinp1main 进程所做的最后一件事,这将在 7 秒后发生。在此之前p2 不应被main 进程强行终止。由于p2 有 5 秒的睡眠时间,它应该从它中唤醒并在 p1 的 7 秒睡眠结束之前打印 p2 Exiting。这里不是这种情况。
    • 主进程强制终止守护进程作为其关闭活动的一部分(在等待 p1 之前,这是它所做的最后一件事 - 多处理的规范不会强制顺序如此但他们也不以任何方式、形式或形式禁止它); p2 是一个守护进程;因此,p2 被强制终止。不知道为什么你会期望其他,但是,无论你期望什么,它都没有遵循规范,并且(没有任何违反规范)它不会发生。
    • 主进程可以(并且确实)执行它“想要”的所有关闭活动(在当前实现中,所有这些活动:-),只要它实际上直到 p1 才退出也退出了。根据docs.python.org/2/library/multiprocessing.html 的规范是:“当进程退出时,它会尝试终止其所有守护进程......非守护进程将自动加入”。规范中没有限制这些终止活动(以及其他活动,例如调用atexit-registers 函数)的完成顺序。当然,如果需要,可以更改延迟!
    • @RatDon,当您明确地join 时,显然是在 主进程的终止操作之前完成的,因为该语句是主进程显式代码的一部分。
    • @RatDon,不客气,但赞成和理想情况下接受会比“谢谢”更好:-)
    【解决方案2】:

    Python 多进程|守护进程和加入

    问题最终增加了对守护进程标志和加入方法的行为的等待,因此这里用一个简单的脚本进行快速解释。

    1. 守护进程在主程序退出前自动终止,以避免让孤立进程运行但不随主程序终止。所以守护函数留下的任何未完成的进程都不会运行!
    2. 但是,如果在守护函数上调用 join() 方法,则主程序 将等待剩余的进程。

    输入

            import multiprocessing
            import time
            import logging
    
    
            def daemon():
                p = multiprocessing.current_process()
                print('Starting:', p.name, p.pid, flush=True)
                print('---' * 15)
                time.sleep(2)
                print('===' * 15 + ' < ' + f'Exiting={p.name, p.pid}' + ' > ' + '===' * 15, flush=True)
    
    
    
            def non_daemon():
                p = multiprocessing.current_process()
                print('Starting:', p.name, p.pid, flush=True)
                print('---'*15)
                print('===' * 15 + ' < ' + f'Exiting={p.name, p.pid}' + ' > ' + '===' * 15, flush=True)
    
    
            if __name__ == '__main__':
                print('==='*15 + ' < ' + 'MAIN PROCESS' + ' > ' + '==='*15)
                logger = multiprocessing.log_to_stderr(logging.INFO)
                # DAEMON
                daemon_proc = multiprocessing.Process(name='MyDaemon', target=daemon)
                daemon_proc.daemon = True
    
                # NO-DAEMON
                normal_proc = multiprocessing.Process(name='non-daemon', target=non_daemon)
                normal_proc.daemon = False
    
                daemon_proc.start()
                time.sleep(2)
                normal_proc.start()
    
                # daemon_proc.join()
    

    输出无连接方法##

    ============================================= < MAIN PROCESS > =============================================
    Starting: MyDaemon 8448
    ---------------------------------------------
    [INFO/MyDaemon] child process calling self.run()
    [INFO/MainProcess] process shutting down
    [INFO/MainProcess] calling terminate() for daemon MyDaemon
    [INFO/MainProcess] calling join() for process MyDaemon
    [INFO/MainProcess] calling join() for process non-daemon
    Starting: non-daemon 6688
    ---------------------------------------------
    ============================================= < Exiting=('non-daemon', 6688) > =============================================
    [INFO/non-daemon] child process calling self.run()
    [INFO/non-daemon] process shutting down
    [INFO/non-daemon] process exiting with exitcode 0
    
    Process finished with exit code 0
    

    函数 daemon() 'spleeping' 2 秒,因此当 Python 到达脚本底部时,程序关闭但 daemon() 没有。

    • 注意:

    [INFO/MainProcess] calling terminate() for daemon MyDaemon

    现在如果脚本的最后一行 daemon_proc.join() 没有被注释。


    输出+连接方法

    ============================================= < MAIN PROCESS > =============================================
    Starting: MyDaemon 13588
    ---------------------------------------------
    [INFO/MyDaemon] child process calling self.run()
    ============================================= < Exiting=('MyDaemon', 13588) > =============================================
    [INFO/MyDaemon] process shutting down
    [INFO/MyDaemon] process exiting with exitcode 0
    [INFO/MainProcess] process shutting down
    [INFO/MainProcess] calling join() for process non-daemon
    Starting: non-daemon 13608
    ---------------------------------------------
    ============================================= < Exiting=('non-daemon', 13608) > =============================================
    [INFO/non-daemon] child process calling self.run()
    [INFO/non-daemon] process shutting down
    [INFO/non-daemon] process exiting with exitcode 0
    
    Process finished with exit code 0
    

    【讨论】: