线程和进程混合后表现异常

3
我正在运行以下Python代码:
import threading
import multiprocessing

def forever_print():
    while True:
        print("")

def main():
    t = threading.Thread(target=forever_print)
    t.start()
    return


if __name__=='__main__':
    p = multiprocessing.Process(target=main)
    p.start()
    p.join()
    print("main process on control")

它结束了。
当我将main从新进程中解包,并直接像这样直接运行它时:
if name == '__main__':
    main()

这段文字的大意是:脚本一直在运行,这正是我所期望的。如果假设非守护进程 t 不会停止,在第一种情况下 p 会停止吗?我设置了一个小测试,因为我一直在开发一个应用程序,在该应用程序中,线程会在子进程内创建,并且表现出一些奇怪的行为(有时会正常终止,有时不会)。我想我想要知道的更广泛的问题是,当混合使用这两个 Python 库时是否存在某种“陷阱”。我的运行环境是:Python 2.7 @ Ubuntu 14.04 LTS。
3个回答

2
您需要在您的main函数中调用t.join()
当您的main函数返回时,该进程将终止其两个线程。 p.join()阻塞主线程等待生成的进程结束。然后,您的生成进程创建一个线程,但不等待其结束。它立即返回,从而破坏了线程本身。
如果线程共享内存,则进程不会共享内存。因此,在新生成的进程中创建的线程仅限于该进程。父进程不知道它的存在。

是的,但是 'join' 不会让我的主线程等待 t 停止以便继续执行吗? - bsam
生成的进程不应该在main终止时继续运行吗?这不是t.daemon=False的作用吗? - bsam

2
目前,由 multiprocessing 工作进程创建的线程在进程终止方面行为类似于守护线程:工作进程退出时不等待其创建的线程终止。这是因为工作进程使用 os._exit() 关闭,跳过了大部分正常关闭处理(特别是跳过了非守护线程的.join() 正常退出处理代码 (sys.exit()) )。
最简单的解决方法是让工作进程显式地 .join() 非守护线程。
目前有一个关于此行为的未解决错误报告,但进展不大:http://bugs.python.org/issue18966

有没有一种解决方法,不像 .join() 那样涉及到阻塞工作进程的呢? - bsam
抱歉,我不明白你的问题。如果在你喜欢的情况下脚本一直运行,那么唯一的原因是主进程被阻塞等待“join()”它创建的线程。还有其他方法可以防止进程退出直到线程完成吗?在另一个情况下也是一样的:在工作进程要退出的位置,只需“join()”它创建的线程即可。该进程无论如何都要退出,所以它没有更好的事情要做;-) - Tim Peters
对不起,我想我完全误解了。你所说的“主进程块等待.join()创建的线程”是什么意思? - bsam
在几乎所有的操作系统上,_所有_线程都会在它们所属的进程退出后终止(创建它们的_thread_是无关紧要的)。"守护进程"与否只与进程是否等待线程完成而退出有关。在Python中,正常的进程退出(解释器关闭)代码的一部分是一个循环,它.join()了每个非守护threading.Thread。这是在Python实现中(threading.py中的私有函数_shutdown())[续下评论] - Tim Peters
显示剩余2条评论

2
重点在于,multiprocessing 机制会在目标函数退出后调用 os._exit(),这会强制杀死子进程,即使它还有后台线程在运行。 Process.start() 的代码如下:
def start(self):
    '''
    Start child process
    '''
    assert self._popen is None, 'cannot start a process twice'
    assert self._parent_pid == os.getpid(), \
           'can only start a process object created by current process'
    assert not _current_process._daemonic, \
           'daemonic processes are not allowed to have children'
    _cleanup()
    if self._Popen is not None:
        Popen = self._Popen
    else:
        from .forking import Popen
    self._popen = Popen(self)
    _current_process._children.add(self)

Popen.__init__的形式如下:

        def __init__(self, process_obj):
        sys.stdout.flush()
        sys.stderr.flush()
        self.returncode = None

        self.pid = os.fork()  # This forks a new process
        if self.pid == 0: # This if block runs in the new process
            if 'random' in sys.modules:
                import random
                random.seed()
            code = process_obj._bootstrap()  # This calls your target function
            sys.stdout.flush()
            sys.stderr.flush()
            os._exit(code)  # Violent death of the child process happens here

_bootstrap方法是实际执行您传递给Process对象的目标函数的方法。在您的情况下,那就是main。即使进程没有退出,因为仍然有一个非守护线程在运行,main在启动后立即返回。但是,一旦执行到os._exit(code),子进程将被杀死,而不管是否仍有非守护线程在执行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接