为什么subprocess.Popen不等待子进程终止?

时间:2009-10-09 00:30:32

标签: python mysql

我遇到了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. 选择没有立即看到正确的行数 - 根据一个比我更了解mysql的朋友,这不应该发生
  4. 我错过了什么?

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

3 个答案:

答案 0 :(得分:20)

subprocess.Popen在实例化时运行程序。但是,它不会等待它 - 它会在后台触发它,就好像你在shell中键入cmd &一样。所以,在上面的代码中,你基本上定义了一个竞争条件 - 如果插入可以及时完成,它将显示正常,但如果没有,你会得到意外的输出。您没有等待第一个run()'PID完成,您只需返回其Popen实例并继续。

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

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

但我同意,可以改进此模块的文档。

要等待程序完成,我建议您使用subprocess的便捷方法subprocess.call,或在communicate对象上使用Popen(对于你什么时候需要stdout)。你已经为第二次通话做了这个。

### 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注入,而不用担心引用,如下所示:

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

这甚至会起作用,并且不会像你期望的那样注入:

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

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

答案 1 :(得分:7)

如果你不是绝对需要使用subprocess和popen,那么使用os.system通常会更简单。例如,对于快速脚本,我经常做这样的事情:

import os
run = os.system #convenience alias
result = run('mysql -u ve --execute="select * from wherever" test')

与popen不同,os.system等待你的进程返回,然后再进入脚本的下一个阶段。

文档中的更多信息:http://docs.python.org/library/os.html#os.system

答案 2 :(得分:3)

老兄,为什么你认为subprocess.Popen使用wait方法返回了一个对象,除非是因为等待 NOT 隐含,内在,立即和不可避免,因为你似乎猜测......?!产生子进程的最常见原因不是立即等待它完成,而是让它继续(例如在另一个核心上,或者最坏的时候切片 - 这是操作系统的 - 和硬件 - 了望)在父进程继续的同时;当父进程需要等待子进程完成时,它显然会在原始wait调用返回的对象上调用subprocess.Process