2012-07-23 76 views
8

我需要将文件从一个位置复制到另一个位置,并且如果该文件已存在于目标(不覆盖),我需要引发异常(或至少以某种方式识别)。安全的原子文件复制操作

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

有没有这样做的内置方式,或者有没有办法将动作定义为原子?

+0

它只是创建需要是原子的目的地,但也有源内容,如阅读,只代表一个时间点? – 2012-07-23 14:57:49

+0

只是创造。我正在编写一个程序,将区域文件复制到/ tmp,进行必要的更改,然后在最后复制它。我只需要确定两个人是否同时尝试和编辑,其中一个人不会失去他们的变化。 – Rory 2012-07-23 15:10:55

+2

请注意,如果源和目标位于同一个文件系统上,那么'rename()'只是原子 - 所以您可能想要在目标目录中创建临时文件,而不是在'/ tmp'中。 – 2012-07-23 15:26:52

回答

7

其实办法做到这一点,原子和安全,提供的所有演员都以相同的方式。这是lock-free whack-a-mole algorithm的适应,而不是完全微不足道的,可以随意去与“无”为一般的答案;)

怎么办

  1. 检查文件是否已经存在。如果它确实停止。
  2. Generate a unique ID
  3. 将源文件复制到具有临时名称的目标文件夹,如<target>.<UUID>.tmp
  4. 重命名副本<target>-<UUID>.mole.tmp
  5. Look for any other files matching the pattern<target>-*.mole.tmp
    • 如果他们的UUID比你大,attempt to delete it。 (不要担心,如果它消失了。)
    • 如果他们的UUID比较小于你的,尝试删除你自己的。 (再次,不要担心,如果它消失了。)从现在起,把他们的UUID当作自己的UUID来对待。
  6. 再次检查目的地文件是否已经存在。如果是这样,请尝试删除临时文件。 (不要担心,如果它消失了,请记住您的UUID在步骤5中可能已更改。)
  7. 如果您尚未尝试在步骤6中删除它,请尝试将您的临时文件重命名为其最终名称<target>。 (不要担心,如果它消失了,只需跳回步骤5.)

你完成了!

它是如何工作

试想每个候选源文件即将出洞的痣。在中途停下来之前,它会暂停并将任何竞争性的痣拨回地面,然后检查是否没有其他痣完全出现。如果你在脑中运行这个过程,你应该看到只有一个痣会使它完全消失。为了防止这个系统从livelocking,我们添加一个总的顺序,哪个鼹鼠可以击中哪个。巴姆! A  博士论文  lock-free algorithm

第4步可能看起来不必要—为什么不只是在第一个地方使用该名称?但是,另一个过程可能会在步骤5中“采用”您的     文件,并使其成为第7步中的赢家,所以非常重要的是您不会写出内容!在同一个文件系统上重命名是原子的,所以第4步是安全的。

+1

这是一个漂亮的算法。我不认为我会这么做IRL,但这种方法很巧妙。 – Rory 2015-05-12 18:26:57

+0

这是否依赖uuid产生增量值? – coolfeature 2016-10-03 13:33:45

+0

@coolfeature不,订购仅用于确保最终选择赢家。 – 2016-10-03 15:46:02

11

有没有办法做到这一点;文件复制操作从来不是原子的,也没有办法使它们成为可能。

但是你可以将该文件写入一个随机的临时名称,然后重命名为它。重命名操作必须是原子操作。 如果文件已经存在,重命名将会失败,并且会出现错误。

[编辑2]rename()只有在同一文件系统中执行时才是原子。安全的方法是在与目的地相同的文件夹中创建新文件。

[编辑]有很多讨论重新命名是否总是原子性和关于覆盖行为。所以我挖掘了一些资源。

在Linux上,如果目标存在,并且源和目标都是文件,则目标将被自动覆盖(man page)。所以我错了。

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

在Windows上,如果另一个进程当前正在写入该文件,如果您尝试打开该文件以进行写入,则会出现错误,因此Windows的优势如下。

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

也没有其他的方法,更好地工作,甚至就像这一个。

您可以使用文件锁定代替。但是这样做会使一切变得更加复杂,并且不会产生额外的优势(除了由于某些原因,一些人认为这是一个巨大的优势更复杂)。当你的文件在网络驱动器上时,你会添加很多不错的角落案例。

如果文件已经存在,您可以使用open(2)的模式O_CREAT,这将导致函数失败。但这并不妨碍第二个进程删除该文件并编写自己的副本。

或者你可以创建一个锁目录,因为创建目录也必须是原子的。但那也不会让你买得太多。你必须自己编写锁代码并绝对保证,100%确定你真的,真的会在发生灾难的情况下删除锁目录 - 你不能。

+0

只有在重命名之前刷新和同步。 – dcolish 2012-07-23 14:47:31

+0

出于好奇,这部作品的重命名部分如何? 'os.rename'在Unix上不起作用。 – mgilson 2012-07-23 14:49:13

+0

@dcolish不需要同步 - 冲洗或close()就足够了。 – 2012-07-23 14:49:23