【问题标题】:Daemon thread launching software won't die守护线程启动软件不会死
【发布时间】:2014-07-29 18:25:55
【问题描述】:

我正在尝试编写一个小脚本,它将使用 plink.exe(来自同一文件夹)创建一个 ssh 隧道(在 Windows 上)。 我基本上是使用os.system 来启动命令:

import time
import threading
from os.path import join, dirname, realpath
pc_tunnel_command = '-ssh -batch -pw xxxx -N -L 1234:host1:5678 user@host2'

if __name__ == '__main__':
    t = threading.Thread(target = os.system, \
                         args = (join(dirname(realpath(__file__)), 'plink.exe ') + \
                                      pc_tunnel_command,))
    t.daemon = True
    t.start()
    #without this line it will die. I guess that plink doesn't have enough time to start.
    time.sleep(5) 
    print 'Should die now'

但是,线程(和 plink.exe)似乎继续运行。为什么会这样?有什么方法可以强制线程关闭?启动 plink 的更好方法?

我希望 plink.exe 在我的程序结束时死掉。使用守护线程是我让隧道在后台运行的计划,然后在我的主代码退出时死掉。

顺便说一句 - subprocess.call 也会发生同样的事情。

【问题讨论】:

    标签: python multithreading python-2.7


    【解决方案1】:

    您可以使用atexitsignal 模块注册回调,当您的程序正常退出或接收到SIGTERM 时,它们将显式终止进程:

    import sys
    import time
    import atexit
    import signal
    import subprocess
    from functools import partial
    from os.path import join, dirname, realpath
    
    pc_tunnel_command = '-ssh -batch -pw xxxx -N -L 1234:host1:5678 user@host2'
    
    
    def handle_exit(p, *args):
        print("killing it")
        p.terminate()
        sys.exit(0)
    
    if __name__ == '__main__':
        p = subprocess.Popen(join(dirname(realpath(__file__)), 'plink.exe ') + pc_tunnel_command, shell=True)
        func = partial(handle_exit, p)
        signal.signal(signal.SIGTERM, func)
        atexit.register(func)
        print 'Should die now'
    

    关于您所描述的行为的一件奇怪的事情是,我希望您的程序在您的 sleep 调用之后退出,但让 plink 在后台运行,而不是让您的程序挂起直到 os.system通话完成。这就是我在 Linux 上看到的行为,至少。无论如何,显式终止子进程应该可以为您解决问题。

    【讨论】:

    • 效果很好。谢谢。有几个问题,如果可以的话: 1. 为什么sys.exit(0) 必须在handle_exit 结尾处调用? 2. 为什么需要partial 部分? (它似乎在没有他们的情况下工作)
    • @Korem 当您使用signal.signal(signal.SIGTERM, ...)您的程序将吞下 SIGTERM 并调用您的处理程序。因此,如果您没有明确告诉您的程序在处理程序中退出,它会在处理完SIGTERM 后继续运行。在这种情况下,您希望在收到SIGTERM 时退出,但只需先进行清理。使用partial 以便我们可以将Popen 对象传递给handle_exitsignal.signal API 不提供将参数传递给您提供的回调函数的方法;使用 partial 可以解决这个问题。
    • 哦,我在你最近的编辑之前写了我的评论,当时signal.signal(signal.SIGTERM, func) 行被注释掉了。没有它似乎也能正常工作。你为什么同时使用这两个?
    • @Korem atexit 将在您的程序正常退出时运行,但如果它被信号终止(如SIGTERM)则不会运行。在 Windows 上,我认为这可能只有在有人使用任务管理器结束进程时才会发生。使用signal.signal 可以让我们处理这种情况并仍然正确清理。
    • @Korem 另请注意,atexit.register 可以为回调函数接受参数,因此您可以使用atexit.register(handle_exit, p)。只是signal.signal 需要partial
    【解决方案2】:

    os.system 在子进程退出之前不会返回。 subprocess.call 也是如此。这就是为什么你的线程坐在那里,等待 plink 完成。您可能可以使用subprocess.Popen 异步启动进程然后退出。无论如何,您创建的附加线程是不必要的。

    【讨论】:

    • 我不同意,并认为您可能误解了我的意思。我希望 plink.exe 在我的程序结束时死掉。使用守护线程是我让隧道在后台运行的计划,然后在我的主代码退出时死掉。我会想象守护线程会收到某种“kill”信号,因此os.system 调用会突然停止。我知道它在退出之前不会返回。顺便说一句,使用 Popen 也会挂起。
    • @Korem 您可能需要显式终止该进程,请参阅this answer
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-28
    相关资源
    最近更新 更多