在 Windows 上使用 python 2.7.4(注意:WinXP - 下面的评论者建议这在 Win7 上正常工作),我有一个脚本,它创建了几个线程,每个线程通过 Popen 运行一个子进程,并将 stdout/stderr 重定向到文件和调用等待()。每个 Popen 都有自己的 stdout/stderr 文件。在每个进程返回后,我有时必须删除文件(实际上将它们移动到其他地方)。
我发现在所有 wait() 调用返回之前,我无法删除 stdout/stderr 日志。在此之前,我收到“WindowsError: [Error 32] The process cannot access the file because it is being used by another process”。只要至少有一个子进程打开,Popen 似乎就会以某种方式保留 stderr 文件,即使这些文件没有共享。
测试代码在下面重现。
C:\test1.py
import subprocess
import threading
import os
def retryDelete(p, idx):
while True:
try:
os.unlink(p)
except Exception, e:
if "The process cannot access the file because it is being used by another process" not in e:
raise e
else:
print "Deleted logs", idx
return
class Test(threading.Thread):
def __init__(self, idx):
threading.Thread.__init__(self)
self.idx = idx
def run(self):
print "Creating %d" % self.idx
stdof = open("stdout%d.log" % self.idx, "w")
stdef = open("stderr%d.log" % self.idx, "w")
p = subprocess.Popen("c:\\Python27\\python.exe test2.py %d" % self.idx,
stdout=stdof, stderr = stdef)
print "Waiting %d" % self.idx
p.wait()
print "Starting deleting logs %d" % self.idx
stdof.close()
stdef.close()
retryDelete("stderr%d.log" % self.idx, self.idx)
print "Done %d" % self.idx
threads = [Test(i) for i in range(0, 10)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
c:\test2.py:
import time
import sys
print "Sleeping",sys.argv[1]
time.sleep(int(sys.argv[1]))
print "Exiting",sys.argv[1]
如果您运行此程序,您将看到每个 retryDelete() 都在文件访问错误上旋转,直到所有子进程都完成为止。
更新:即使没有将 stdof 和 stdef 文件描述符传递给 Popen 构造函数,也会发生此问题。但是,如果 Popen 被删除并且 wait() 被 time.sleep(self.idx) 替换,它不会发生(即删除立即发生)。由于 Popen 似乎对未传递给它的文件描述符有影响,我想知道这个问题是否与句柄继承有关。
更新: close_fds=True 给出一个错误(在重定向标准输出/标准错误时在 Windows 上不支持),并且在 wait() 调用之后使用 del p 删除 Popen 对象对问题没有任何影响。
更新:使用 sysinternals 进程资源管理器查找具有文件句柄的进程。将测试减少到只有 2 个线程/子线程,并使第二个线程长时间保持打开状态。句柄搜索显示唯一具有 stderr0.log 句柄的进程是父 python 进程,它有两个打开的句柄。
更新:对于我当前的紧急使用,我找到了一种解决方法,即创建一个单独的脚本,该脚本将命令行和 stderr/stdout 日志文件作为参数并运行重定向的子进程。然后父级只需使用 os.system() 执行此帮助程序脚本。日志文件随后被成功释放并被删除。但是,我仍然对这个问题的答案很感兴趣。对我来说,这感觉像是一个特定于 WinXP 的错误,但我仍然有可能只是做错了什么。