os.path.exists() 说谎

17 投票
2 回答
8427 浏览
提问于 2025-04-16 00:24

我在一个Linux集群上运行多个Python脚本,一个任务的输出通常是另一个脚本的输入,这些脚本可能在不同的节点上运行。我发现,当文件在其他节点上创建后,Python需要一些时间才能注意到这些文件——os.path.exists()返回的是假,open()也会失败。我可以用一个循环来检查文件是否存在,直到它出现,但这个过程可能需要超过一分钟,这在有很多步骤和可能同时运行多个数据集的情况下并不理想。

到目前为止,我找到的唯一解决办法是调用subprocess.Popen("ls %s"%(pathdir), shell=True),这神奇地解决了问题。我觉得这可能是系统的问题,但Python有没有可能导致这个问题呢?是不是某种缓存之类的?我的系统管理员到现在为止也没有提供太多帮助。

2 个回答

1

这个问题跟Python程序在自己的环境中运行有关。当你使用 subprocess.Popen(shell=True) 时,你是在启动一个新的环境,这样做是为了绕过你遇到的问题。

其实,问题并不是Python引起的,而是因为NFS(文件存储)和Linux中目录列表的工作方式结合在一起造成的。

17

os.path.exists() 这个函数其实就是在调用C语言库里的 stat() 函数。

我觉得你遇到的问题可能是因为内核的NFS(网络文件系统)实现中有缓存。下面有个链接,里面描述了这个问题以及一些清除缓存的方法。

文件句柄缓存

目录会缓存文件名和文件句柄的映射。最常见的问题有:

•你打开了一个文件,但需要检查这个文件是否被新文件替换了。在 stat() 返回新文件信息之前,你必须先清除父目录的文件句柄缓存,这样才能得到新文件的信息,而不是打开的那个文件的信息。

◦实际上,这种情况还有另一个问题:旧文件可能已经被删除并被新文件替换,但这两个文件可能有相同的inode(索引节点)。你可以通过清除打开文件的属性缓存,然后查看 fstat() 是否返回ESTALE来检查这种情况。

•你需要检查一个文件是否存在,比如一个锁文件。内核可能已经缓存了这个文件不存在的信息,即使实际上它是存在的。你必须清除父目录的负面文件句柄缓存,才能确认这个文件是否真的存在。

几种清除文件句柄缓存的方法:

•如果父目录的修改时间(mtime)发生了变化,文件句柄缓存会通过清除其属性缓存而被清除。如果NFS服务器支持纳秒或微秒的精度,这种方法应该效果很好。

•在Linux中:使用 chown() 命令将目录的所有者改为当前所有者。如果这个调用成功返回,文件句柄缓存就会被清除。

•在Solaris 9和10中:唯一的方法是尝试使用 rmdir() 删除父目录。如果返回ENOTEMPTY,说明缓存已被清除。尝试删除当前目录会失败并返回EINVAL,但不会清除缓存。

•在FreeBSD 6.2中:唯一的方法是尝试删除父目录或其下的文件。如果返回ENOTEMPTY、ENOTDIR和EACCES,说明缓存已被清除,但如果返回ENOENT则没有清除。FreeBSD不缓存负面条目,所以不需要清除。

http://web.archive.org/web/20100912144722/http://www.unixcoding.org/NFSCoding

撰写回答