安全的原子文件复制操作

Ror*_*ory 10 python concurrency

我需要将文件从一个位置复制到另一个位置,如果文件已存在于目标位置(无覆盖),我需要抛出异常(或至少以某种方式识别).

我可以先用os.path.exists()检查,但是在检查和复制之间的少量时间内无法创建文件是非常重要的.

是否有内置的方法来实现这一点,或者有没有办法将动作定义为原子?

Aar*_*lla 13

没有办法做到这一点; 文件复制操作永远不是原子的,也没有办法制作它们.

但您可以使用随机的临时名称编写该文件,然后重命名该文件.重命名操作必须是原子的.如果该文件已存在,则重命名将失败,您将收到错误.

rename()如果在同一文件系统中执行,[EDIT2]只是原子的.安全的方法是在与目标相同的文件夹中创建新文件.

[编辑]有很多关于重命名是否始终是原子的以及关于覆盖行为的讨论.所以我挖出了一些资源.

在Linux上,如果目标存在且源和目标都是文件,则会以静默方式覆盖目标(手册页).所以我错了.

但是rename(2)仍然保证原始文件或新文件在出现问题时仍然有效,因此操作是原子的,因为它不能破坏数据.从某种意义上讲,它不是原子的,它可以阻止两个进程同时进行相同的重命名,并且可以预测结果.一个人会赢,但你不知道哪个.

在Windows上,如果另一个进程当前正在编写该文件,如果您尝试将其打开以进行编写,则会出现错误,因此Windows的一个优点就在这里.

如果在将操作写入磁盘时计算机崩溃,则文件系统的实现将决定损坏的数据量.有没有什么应用程序可以做到这一点.所以停止抱怨已经:-)

也没有其他方法可以更好地工作,甚至也不如此方法.

您可以使用文件锁定.但这只会使一切变得更加复杂并且不会产生额外的优势(除了更复杂,有些人确实认为这是一个巨大的优势).当你的文件在网络驱动器上时,你会添加许多漂亮的角落案例.

如果文件已存在,您可以使用会使函数失败open(2)的模式O_CREAT.但这不会阻止第二个进程删除文件并编写自己的副本.

或者你可以创建一个锁目录,因为创建目录也必须是原子的.但这也不会给你带来太大的收益.你必须亲自编写锁定代码,并且绝对,100%确定你真的,真的总是在发生灾难时删除锁定目录 - 你不能这样做.

  • @dcolish那是写入磁盘的.从文件系统操作的角度来看,您不需要从磁盘转到原子,只是为了防止崩溃. (2认同)

Ali*_*ell 9

还有就是其实是一个办法做到这一点,原子和安全,提供的所有演员都以相同的方式.它是对无锁whack-a-mole算法的改编,并不是完全无关紧要的,所以请随意使用"否"作为一般答案;)

该怎么办

  1. 检查文件是否已存在.如果确实停止.
  2. 生成唯一ID
  3. 例如,使用临时名称将源文件复制到目标文件夹<target>.<UUID>.tmp.
  4. 重命名副本<target>-<UUID>.mole.tmp.
  5. 查找与该模式匹配的任何其他文件 <target>-*.mole.tmp.
    • 如果他们的UUID比你的UUID更大,请尝试删除它.(如果它消失了,别担心.)
    • 如果他们的UUID比你的UUID少,请尝试删除你自己的UUID.(再次,如果它已经消失,请不要担心.)从现在开始,将其UUID视为您自己的UUID.
  6. 再次检查以查看目标文件是否已存在.如果是这样,请尝试删除临时文件.(如果它已经消失,请不要担心.请记住,您的UUID可能在步骤5中已更改.)
  7. 如果您尚未尝试在步骤6中删除它,请尝试将临时文件重命名为其最终名称<target>.(不要担心,如果它消失了,只需跳回到第5步.)

你完成了!

这个怎么运作

想象一下,每个候选源文件都是一个鼹鼠.在检查没有其他鼹鼠完全出现之前,它会暂停并将任何竞争的鼹鼠重击回地面.如果你在头脑中经历这一过程,你应该看到只有一颗痣会完全消失.为了防止这个系统发生活,我们添加了一个可以敲打哪个痣的总排序.巴姆!一个 博士论文  的无锁算法.

步骤4可能看起来不必要 - 为什么不首先使用该名称?但是,另一个过程可能会在步骤5中"采用"你的 鼹鼠 文件,并使其成为第7步中的胜利者,所以你还没有写出内容是非常重要的!在同一文件系统上重命名是原子的,因此第4步是安全的.