繁体   English   中英

如何在获取排他锁避免竞争的同时打开(如果不存在则创建)文件

[英]how to open (create if not exists) a file while acquiring exclusive lock avoiding races

在 python 2.7 中,是否有可能(以及如何)在单个原子(无竞争)操作中进行:

  • 打开文件
    • 如果它不存在,请创建然后打开它。
  • 获得文件的排他锁(没有其他进程可以打开或删除文件)

上下文:我有一个 Python 程序,可以根据 URL/md5 列表获取文件; 如果列表中的文件存在并且它的 md5 匹配,则会跳过它。 如果没有,它将被下载。 现在,该程序可能有多个实例处理可能重叠的不同列表。

这个问题几乎是我需要做的,但在我的情况下,我需要以任何一种方式锁定文件以检查它的 md5,同时防止其他人这样做。 另外,我不需要在操作之前知道文件是否存在; 如果它刚刚创建,该文件将为空,并且它的 md5 将不匹配,因此无论如何都会下载它。

我专门在 Linux 上使用这个程序,但欢迎跨平台解决方案。


编辑:最后我通过以下方式解决了我的问题:

  • 以 a+b 模式打开文件(非原子,如果不存在则创建)。
  • 尝试以独占方式锁定文件(建议):
    • 如果成功,请处理文件。
    • 如果失败,假设其他人正在处理该文件并跳到下一个。 在没有更多文件要处理后,回来检查锁定文件的人是否做得对。

就目前而言,单个原子步骤不支持所需的操作,但也不需要。

至少根据这份综合报告,这是不可能的:

  • mv -T <oldsymlink> <newsymlink>原子改变所述目标<newsymlink>到目录指向<oldsymlink>和部署新代码时,是不可缺少的。 2010-01-06 更新:两个操作数都是符号链接。 (所以这不是系统调用,它仍然有用。)一位读者指出ln -Tfs <directory> <symlink>完成相同的事情,而无需第二个符号链接。 添加 2010-01-06。 2010 年 1 月 6 日删除:strace(1) 显示ln -Tfs <directory> <symlink>实际上ln -Tfs <directory> <symlink>调用了 symlink(2)、unlink(2) 和 symlink(2),从而取消了它从此页面的资格。 mv -T <oldsymlink> <newsymlink>最终调用 rename(2) ,它可以原子地替换<newsymlink> 警告 2013-01-07:这不适用于 Mac OS X,其 mv(1) 不调用 rename(2)。 MV(1)。
  • link(oldpath, newpath)创建一个名为 newpath 的新硬链接,指向与 oldpath 相同的 inode 并将链接计数增加 1。 如果 newpath 已经存在,这将失败并显示错误代码 EEXIST,这使其成为在线程或进程之间锁定文件的有用机制,这些线程或进程都可以就名称 newpath 达成一致。 我更喜欢这种用于整个文件锁定的技术,因为该锁定对 ls(1) 是可见的。 链接(2)。
  • symlink(oldpath, newpath)操作与 link(2) 非常相似,但在新的 inode 处创建符号链接,而不是到同一 inode 的硬链接。 符号链接可以指向目录,而硬链接不能,这使得它们在锁定整个目录时与 link(2) 完美类似。 如果 newpath 已经存在,这将失败并显示错误代码 EEXIST,这使其成为对目录也适用的 link(2) 的完美类比。 注意目标 inode 已被删除的符号链接(“悬空”符号链接)——open(2) 将失败并显示错误代码 ENOENT。 应该提到的是,inode 是一种有限资源(这台特定的机器有 1,245,184 个 inode)。 符号链接(2)。 添加 2010-01-07
  • rename(oldpath, newpath)可以自动更改路径名,前提是 oldpath 和 newpath 在同一个文件系统上。 如果 oldpath 不存在,这将失败并显示错误代码 ENOENT,启用进程间锁定,就像上面的 link(oldpath, newpath) 一样。 当有问题的文件稍后被取消链接时,我发现这种技术更自然。 重命名(2)。
  • open(pathname, O_CREAT | O_EXCL, 0644)创建并打开一个新文件。 (不要忘记在第三个参数中设置模式!)如果路径名存在,O_EXCL 指示此操作失败并返回错误代码 EEXIST。 这是决定哪个进程应该处理任务的有用方法:成功创建文件的人。 打开(2)。
  • mkdir(dirname, 0755)创建一个新目录,但如果 dirname 存在,则会失败并显示错误代码 EEXIST。 这为目录提供了与 O_EXCL 为文件提供的相同机制 link(2) open(2)。 mkdir(2)。 添加 2010-01-06; 编辑 2013-01-07。

如您所见, open()只能以原子方式用于创建文件,而不能打开现有文件进行读取。 但是,如果您想使用这种方法,您可能需要使用 Python 的os.open() ,它是此系统调用的代理(不要与内置的open()混淆)。

您也可以考虑使用数据库来完成这项任务,因为它们应该提供更高的可靠性(例如,如果您的文件托管在 NFS 上,它根本没有实现锁定并且 IIRC 是唯一的原子操作mkdir() ?)。

不,它不可能作为 Linux/UNIX 支持的基本操作。

您引用的答案中的 O_CREAT|O_EXCL 技术可以在这里工作。 您不是专门创建目标文件,而是专门创建一个锁文件,其名称可预测地从目标文件派生而来。 例如, os.path.join("/tmp", hashlib.md5(target_filename).hexdigest() + ".lock")

但是,正如其他人所建议的那样,您不清楚是否需要保护目标文件创建及其校验和 + 可能的替换。 fcntl咨询锁将满足您的需求。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM