gpt4 book ai didi

python - 为什么 subprocess.Popen 不等到子进程终止?

转载 作者:太空狗 更新时间:2023-10-29 22:00:32 25 4
gpt4 key购买 nike

我在使用 Python 的 subprocess.Popen 方法时遇到问题。

这是一个演示问题的测试脚本。它在 Linux 机器上运行。

#!/usr/bin/env python
import subprocess
import time

def run(cmd):
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
return p

### START MAIN
# copy some rows from a source table to a destination table
# note that the destination table is empty when this script is run
cmd = 'mysql -u ve --skip-column-names --batch --execute="insert into destination (select * from source limit 100000)" test'
run(cmd)

# check to see how many rows exist in the destination table
cmd = 'mysql -u ve --skip-column-names --batch --execute="select count(*) from destination" test'
process = run(cmd)
count = (int(process.communicate()[0][:-1]))

# if subprocess.Popen() waited for the child to terminate than count should be
# greater than 0
if count > 0:
print "success: " + str(count)
else:
print "failure: " + str(count)
time.sleep(5)

# find out how many rows exists in the destination table after sleeping
process = run(cmd)
count = (int(process.communicate()[0][:-1]))
print "after sleeping the count is " + str(count)

通常这个脚本的输出是:

success: 100000

但有时是

failure: 0
after sleeping the count is 100000

请注意,在失败的情况下,插入后的选择立即显示 0 行,但在休眠 5 秒后,第二次选择正确显示行数为 100000。我的结论是以下情况之一为真:

  1. subprocess.Popen 没有等待子线程终止——这似乎与文档相矛盾
  2. mysql 插入不是原子的——我对 mysql 的理解似乎表明插入是原子的
  3. select 没有立即看到正确的行数 - 据一位比我更了解 mysql 的 friend 说,这也不应该发生

我错过了什么?

仅供引用,我知道这是一种从 Python 与 mysql 交互的 hacky 方式,而 MySQLdb 可能不会有这个问题,但我很好奇为什么这种方法不起作用。

最佳答案

subprocess.Popen,在实例化时运行程序。但是,它不会等待它——它会在后台触发它,就像您在 shell 中键入 cmd & 一样。因此,在上面的代码中,您实际上已经定义了一个竞争条件——如果插入可以及时完成,它看起来是正常的,但如果不是,您将得到意想不到的输出。您不是在等待第一个 run() 的 PID 完成,您只是返回它的 Popen 实例并继续。

我不确定这种行为如何与文档相矛盾,因为 Popen 上有一些非常明确的方法似乎表明它没有被等待,比如:

Popen.wait()
Wait for child process to terminate. Set and return returncode attribute.

不过,我同意该模块的文档可以改进。

要等待程序完成,我建议使用 subprocess 的便捷方法,subprocess.call,或使用 communicatePopen 对象上(对于需要标准输出的情况)。您已经在为第二次通话执行此操作。

### START MAIN
# copy some rows from a source table to a destination table
# note that the destination table is empty when this script is run
cmd = 'mysql -u ve --skip-column-names --batch --execute="insert into destination (select * from source limit 100000)" test'
subprocess.call(cmd)

# check to see how many rows exist in the destination table
cmd = 'mysql -u ve --skip-column-names --batch --execute="select count(*) from destination" test'
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
try: count = (int(process.communicate()[0][:-1]))
except: count = 0

此外,在大多数情况下,您不需要在 shell 中运行命令。这是其中一种情况,但您必须像序列一样重写命令。这样做还可以让您避免传统的 shell 注入(inject),并且不用担心引用,就像这样:

prog = ["mysql", "-u", "ve", "--execute", 'insert into foo values ("snargle", 2)']
subprocess.call(prog)

这甚至可以工作,并且不会像您期望的那样注入(inject):

prog = ["printf", "%s", "<", "/etc/passwd"]
subprocess.call(prog)

以交互方式尝试。您可以避免 shell 注入(inject)的可能性,尤其是在您接受用户输入时。我怀疑您正在使用不太棒的字符串方法与子进程进行通信,因为您在使序列正常工作时遇到了麻烦:^)

关于python - 为什么 subprocess.Popen 不等到子进程终止?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1541273/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com