在 Python 中创建竞争条件文件夹

Posted

技术标签:

【中文标题】在 Python 中创建竞争条件文件夹【英文标题】:Race-condition creating folder in Python 【发布时间】:2010-12-07 21:11:53 【问题描述】:

我有一个 urllib2 缓存模块,由于以下代码偶尔会崩溃:

if not os.path.exists(self.cache_location):
    os.mkdir(self.cache_location)

问题是,在执行第二行的时候,文件夹可能已经存在,会报错:

 文件“.../cache.py”,第 103 行,在 __init__ 中
    os.mkdir(self.cache_location)
OSError: [Errno 17] 文件存在:'/tmp/examplecachedir/'

这是因为脚本多次同时启动,第三方代码我无法控制。

代码(在我尝试修复错误之前)可以找到here, on github

我不能使用 tempfile.mkstemp,因为它通过使用随机命名的目录 (tempfile.py source here) 来解决竞争条件,这会破坏缓存的用途。

我不想简单地丢弃错误,因为如果文件夹名称作为文件存在(不同的错误),则会引发相同的错误 Errno 17 错误,例如:

$ 触摸等等
$蟒蛇
>>> 导入操作系统
>>> os.mkdir("废话")
回溯(最近一次通话最后):
  文件“”,第 1 行,在
OSError:[Errno 17] 文件存在:'blah'
>>>

我不能使用threading.RLock,因为代码是从多个进程调用的。

所以,我尝试编写一个简单的基于文件的锁 (that version can be found here),但这有一个问题:它会在上一级创建锁文件,因此 /tmp/example.lock 用于 /tmp/example/,如果使用 @987654329,则会中断@ 作为缓存目录(因为它试图使/tmp.lock)..

简而言之,我需要将urllib2 响应缓存到磁盘。为此,我需要以多进程安全的方式访问一个已知目录(如果需要,创建它)。它需要在 OS X、Linux 和 Windows 上运行。

想法?我能想到的唯一替代解决方案是使用 SQLite3 存储而不是文件来重写缓存模块。

【问题讨论】:

【参考方案1】:

代替

if not os.path.exists(self.cache_location):
    os.mkdir(self.cache_location)

你可以的

try:
    os.makedirs(self.cache_location)
except OSError:
    pass

正如你最终会得到相同的功能

免责声明:我不知道这可能是 Pythonic。


使用SQLite3可能有点矫枉过正,但会为您的用例增加很多功能和灵活性。

如果您必须进行大量“选择”、并发插入和过滤,最好使用SQLite3,因为它不会比简单文件增加太多复杂性(可以说它消除了复杂性) .


重读您的问题(和 cmets)我可以更好地理解您的问题。

文件可能产生相同的竞争条件的可能性有多大?

如果它足够小,那么我会这样做:

if not os.path.isfile(self.cache_location):
    try:
        os.makedirs(self.cache_location)
    except OSError:
        pass

另外,阅读你的代码,我会改变

else:
    # Our target dir is already a file, or different error,
    # relay the error!
    raise OSError(e)

else:
    # Our target dir is already a file, or different error,
    # relay the error!
    raise

因为它真的是你想要的,Python 重新引发完全相同的异常(只是挑剔)


还有一点,this 可能对你有用(仅限类 Unix)。

【讨论】:

同意,不过您可以考虑改用 os.makedirs,如果需要,它也会创建父目录。 在许多情况下,忽略错误将是 Pythonic,但在这种情况下,它可能会导致其他问题,正如我在问题中所述 - “我不想简单地丢弃错误,因为如果文件夹名称作为文件存在(不同的错误),则会引发相同的错误 Errno 17 错误” - 丢弃异常意味着临时目录不存在(例如,权限被拒绝错误),或者是无用的文件(并且会导致错误更进一步) 另外,我同意你关于 SQLite 的观点——我不需要在过滤方面做太多事情(几乎只是“在过去的 x 小时内是否请求过这个 URL”),但是SQLite 将消除很多复杂性(不构造缓存文件路径和检查文件修改时间),我认为它应该处理所有锁定问题(包括缓存数据的特定位和 sqlite 文件本身)。嗯...如果 Pythonic Metaphor 的解决方案不起作用,我可以开始这个(事实上我可以不管,一个好的 SQLite 支持的 urllib2 缓存模块会很有用) @dbr:如果要区分“目录已作为目录存在”和“目录名称已作为文件存在”,请在 except 子句中进行。不要让这个太复杂。只需处理异常。 感谢您的想法。捕获 OSError,检查目录是否存在,如果存在则忽略错误似乎工作正常,如果用户权限不足,仍然会正确出错。我已经对代码进行了您建议的更改,我完全忘记了 raise 将重新引发捕获的异常。【参考方案2】:

我最终得到的代码是:

import os
import errno

folder_location = "/tmp/example_dir"

try:
    os.mkdir(folder_location)
except OSError as e:
    if e.errno == errno.EEXIST and os.path.isdir(folder_location):
        # File exists, and it's a directory,
        # another process beat us to creating this dir, that's OK.
        pass
    else:
        # Our target dir exists as a file, or different error,
        # reraise the error!
        raise

【讨论】:

【参考方案3】:

在 Python 3.x 中,您可以使用os.makedirs(path, exist_ok=True),如果存在此类目录,则不会引发任何异常。如果存在与请求目录 (path) 同名的文件,它将引发 FileExistsError: [Errno 17]

验证它:

import os

parent = os.path.dirname(__file__)

target = os.path.join(parent, 'target')

os.makedirs(target, exist_ok=True)
os.makedirs(target, exist_ok=True)

os.rmdir(target)

with open(target, 'w'):
    pass

os.makedirs(target, exist_ok=True)

【讨论】:

似乎是“exist_ok”而不是“exists_ok”。【参考方案4】:

能否捕捉到异常,然后测试文件是否作为目录存在?

【讨论】:

可能!我在提交之前重新阅读问题时想到了这一点。我已经实现了这个(github.com/dbr/tvdb_api/blob/…),并将让报告错误的人尽快对其进行测试。 @dbr:请注意,在第 114 行,您需要 raise e,因为它已经是 OSError 的一个实例。 github.com/dbr/tvdb_api/blob/…【参考方案5】:

当你有比赛条件时,EAFP(请求宽恕比许可更容易)比 LBYL(跳前看)效果更好

Error checking strategies

【讨论】:

这一切都取决于冲突的可能性 - EAFP = 乐观并发,它在尝试操作后检查冲突,如果冲突概率很小,则效果很好。 LBYL = 悲观并发,它在操作前检查,如果通常有很多冲突会更好。对于这样一个简单的案例,我认为悲观更好。 @RichVel,这里有 LBYL,你有可能出现竞争条件。所以无论如何你都需要额外的代码来处理异常。所以这只是一个简单的操作,现在额外的代码并不多,但是这些东西在项目的生命周期中会不断增长。每次有人需要阅读/理解/调试这个额外的代码时,都会产生额外的成本。我认为在这种情况下,这可能是一个不成熟且不必要的优化。 事实并非如此,因为 mkdir 是原子的,即它等同于单个“测试和设置”操作。使用非原子测试和设置操作编写 LBYL 操作会导致竞争条件,但这是一个实现错误。

以上是关于在 Python 中创建竞争条件文件夹的主要内容,如果未能解决你的问题,请参考以下文章

python-多线程同步中创建互斥锁解决资源竞争的问题

如何在 Spring Boot 的 YML 文件中创建条件属性?

创建锁定文件时防止竞争条件

如何在python中创建一个zip文件

如何在 Python 中创建多个嵌套文件夹?

如何在 Python 中创建 tmp 文件?