我在subprocess
documentation中找到了这段代码,其中一个进程的stdout被传送到另一个进程:
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
output = p2.communicate()[0]
我对stdout.close()
电话感到困惑。当然关闭stdout句柄会阻止进程产生任何输出吗?
所以我进行了一项实验,令我惊讶的是,这个过程完全没有受到影响:
from subprocess import Popen, PIPE
p1 = Popen(['python', '-c', 'import time; time.sleep(5); print(1)'], stdout=PIPE)
p2 = Popen(['python', '-c', 'print(input()*3)'], stdin=p1.stdout, stdout=PIPE)
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
print('stdout closed')
print('stdout:', p2.communicate()[0])
# output:
# stdout closed
# [5 second pause]
# stdout: b'111\n'
这里发生了什么?为什么进程会写入封闭的管道?
答案 0 :(得分:4)
关闭文件描述符只是意味着减少引用计数(在操作系统内核中)。描述符编号变为无效,但它引用的对象没有任何反应,除非引用计数达到零。
在Popen
次调用中,正在进行复制文件描述符的操作,从而增加了引用次数:dup
/ dup2
和fork
等操作。
如果我们fork
子进程从父进程接收文件描述符,则该文件描述符是指向同一对象的副本。
如果父级关闭了该描述符的原始副本,则不会影响该子级中的那个。反之亦然。只有当父和子关闭该描述符时,底层打开的文件/设备对象才会消失;并且只有在没有引用它的其他描述符的情况下。
即使描述符具有相同的数字,也是如此。每个进程都有自己的文件描述符编号表。子项中的文件描述符3与父项中的3不同。
答案 1 :(得分:2)
每个进程都有自己的一组文件描述符。一旦不需要,您可以(并且应该)关闭父母的副本;实际管道(端点)一直存在,直到 last 进程(大概是p2
)关闭它。只有这样,尝试写入(管道的另一端)的进程才会获得SIGPIPE
。
如果你不关闭它,即使在p2
退出后,父仍然可以从中读取,这将不必要地保持p1
活着(并且可以如果父wait
在其上,则会导致死锁。