我有一个长时间运行的 python 脚本,它创建和删除临时文件。我注意到在文件删除上花费了大量时间,但删除这些文件的唯一目的是确保程序最终不会在长时间内填满所有磁盘空间。Python中是否有跨平台机制来异步删除文件,以便在操作系统处理文件删除时主线程可以继续工作?
3 回答
您可以尝试将删除文件委托给另一个线程或进程。
使用新生成的线程:
thread.start_new_thread(os.remove, filename)
或者,使用一个过程:
# create the process pool once
process_pool = multiprocessing.Pool(1)
results = []
# later on removing a file in async fashion
# note: need to hold on to the async result till it has completed
results.append(process_pool.apply_async(os.remove, filename), callback=lambda result: results.remove(result))
进程版本可能允许更多的并行性,因为 Python 线程由于臭名昭著的全局解释器锁而不能并行执行。我希望 GIL 在调用任何阻塞内核函数(例如 )时被释放unlink()
,以便 Python 让另一个线程取得进展。换句话说,调用的后台工作线程os.unlink()
可能是最好的解决方案,请参阅 Tim Peters 的回答。
然而,multiprocessing
正在使用底层的 Python 线程与池中的进程进行异步通信,因此需要进行一些基准测试来确定哪个版本提供更多的并行性。
避免使用 Python 线程但需要更多编码的另一种方法是生成另一个进程并通过管道将文件名发送到其标准输入。这样您os.remove()
就可以使用同步os.write()
(一个write()
系统调用)进行交易。它可以使用 deprecated 来完成,os.popen()
并且该函数的这种用法是非常安全的,因为它只在一个方向上与子进程通信。一个工作原型:
#!/usr/bin/python
from __future__ import print_function
import os, sys
def remover():
for line in sys.stdin:
filename = line.strip()
try:
os.remove(filename)
except Exception: # ignore errors
pass
def main():
if len(sys.argv) == 2 and sys.argv[1] == '--remover-process':
return remover()
remover_process = os.popen(sys.argv[0] + ' --remover-process', 'w')
def remove_file(filename):
print(filename, file=remover_process)
remover_process.flush()
for file in sys.argv[1:]:
remove_file(file)
if __name__ == "__main__":
main()
您可以按照常见的生产者-消费者模式创建一个线程来删除文件:
import threading, Queue
dead_files = Queue.Queue()
END_OF_DATA = object() # a unique sentinel value
def background_deleter():
import os
while True:
path = dead_files.get()
if path is END_OF_DATA:
return
try:
os.remove(path)
except: # add the exceptions you want to ignore here
pass # or log the error, or whatever
deleter = threading.Thread(target=background_deleter)
deleter.start()
# when you want to delete a file, do:
# dead_files.put(file_path)
# when you want to shut down cleanly,
dead_files.put(END_OF_DATA)
deleter.join()
CPython 围绕内部文件删除调用释放 GIL(全局解释器锁),所以这应该是有效的。
编辑 - 新文本
我建议不要在每次删除时产生一个新进程。在某些平台上,进程创建非常昂贵。还建议不要在每次删除时产生一个新线程:在长时间运行的程序中,您真的永远不希望在任何时候创建无限数量的线程。根据文件删除请求堆积的速度,这可能发生在这里。
上面的“解决方案”比其他的更冗长,因为它避免了所有这些。总共只有一个新线程。当然,它可以很容易地推广为使用任何固定数量的线程,所有线程都共享同一个dead_files
队列。从 1 开始,如果需要,可以添加更多;-)
操作系统级别的文件删除原语在 Unix 和 Windows 上都是同步的,所以我认为你几乎必须使用工作线程。您可以让它拉文件以删除 Queue 对象,然后当主线程处理完文件后,它可以将文件发布到队列中。如果您使用的是 NamedTemporaryFile 对象,您可能希望delete=False
在构造函数中设置并将名称发布到队列,而不是文件对象,这样您就不会遇到对象生命周期问题。