我正在尝试使用Popen
创建子进程A
以及使用Popen.communicate
与之通信的线程。主进程将使用具有指定超时的Thread.join
在线程上等待,并在超时到期后终止A
,这将导致线程也死亡。
然而,当A
本身产生更多的子过程B
,C
和D
与不同的过程组而不是A
拒绝死亡时,这似乎不起作用。即使在A
已经死亡并标记为已解散之后,甚至在主要进程使用A
使os.waitpid()
不再存在之后,该线程拒绝加入主线程。
只有在所有的孩子,B
,C
,D
被杀后,Popen.communicate
终于回来了。
这个行为实际上是从模块中预期的吗?在某些情况下,递归等待可能很有用,但它肯定不适合作为Popen.communicate
的默认行为。如果这是预期的行为,有没有办法覆盖它?
这是一个非常简单的例子:
from subprocess import PIPE, Popen
from threading import Thread
import os
import time
import signal
DEVNULL = open(os.devnull, 'w')
proc = Popen(["/bin/bash"], stdin=PIPE, stdout=PIPE,
stderr=DEVNULL, start_new_session=True)
def thread_function():
print("Entering thread")
return proc.communicate(input=b"nohup sleep 100 &\nexit\n")
thread = Thread(target=thread_function)
thread.start()
time.sleep(1)
proc.kill()
while True:
thread.join(timeout=5)
if not thread.is_alive():
break
print("Thread still alive")
这是在Linux上。
我认为这来自于在Linux中编写popen.communicate方法的一种相当自然的方式。 Proc.communicate()似乎读取stdin文件描述符,当进程终止时它将返回EOF。然后它等待获取进程的退出代码。
在您的示例中,sleep进程从bash进程继承stdin文件描述符。因此,当bash进程终止时,popen.communicate没有在stdin管道上获得EOF,因为睡眠仍然打开它。解决此问题的最简单方法是将通信线路更改为:
return proc.communicate(input=b"nohup sleep 100 >/dev/null&\nexit\n")
这会导致你的线程在bash死亡后立即结束...由于退出,而不是你的proc.kill,在这种情况下。但是,如果使用exit语句或proc.kill调用,则在bash死后,sleep仍在运行。如果你想杀死睡眠,我会用
os.killpg(proc.pid,15)
而不是proc.kill()。杀死B,C和D的更一般的问题是,如果他们改变组是一个更复杂的问题。
附加数据:我找不到这种proc.communicate方法的官方文档,但我忘记了最明显的地方:-)我在this answer的帮助下找到了它。 docs for communicate说:
与流程交互:将数据发送到stdin。从stdout和stderr读取数据,直到达到文件结尾。等待进程终止。
您在步骤2中遇到困难:读取直到文件结束,因为睡眠会使管道保持打开状态。