有没有一种与操作系统无关的方法来原子地覆盖文件?

8
如果文件已经存在,我想覆盖它。如果不存在,我想创建它并写入它。我希望不必使用第三方库,比如lockfile(似乎处理所有类型的锁定)。
我的初步想法是:
1. 使用随机生成的大ID写入临时文件,以避免冲突。 2. 重命名临时文件名 -> 新路径名。

3
如果你限制自己在 POSIX 系统中,Rename 是正确的方式。然而,Windows 并不支持所有版本的 Rename,且在 Windows 上 Go 也不支持 Rename。 - JimB
@JimB,你认为第一步怎么样?还有其他(重要的)步骤缺失吗? - lf215
6
对于#1,我会使用ioutil.TempFile(将dir设置为最终目录以便重命名不涉及跨设备复制)来创建临时文件,而不是尝试生成随机文件并自己处理竞态条件。 - Dave C
1
但这不是关于Golang的。 - Jiang YD
对于Windows系统,可能可以使用ReplaceFile https://msdn.microsoft.com/en-us/library/windows/desktop/aa365512(v=vs.85).aspx,该函数支持从XP到当前版本的操作系统,但您需要将其与POSIX重命名函数的shims一起添加到自己的软件包中,以使其跨平台。更多信息请参见https://msdn.microsoft.com/en-us/library/windows/desktop/hh802690(v=vs.85).aspx。 - Intermernet
1个回答

12

os.Rename 调用 syscall.Rename,对于 Linux/UNIX,使用重命名系统调用(原子操作 *)。在 Windows 中,syscall.Rename 调用 MoveFileW,假定源文件和目标文件位于同一设备上(可以安排),并且文件系统为 NTFS(通常如此),也是原子操作 *。

我会注意确保源文件和目标文件位于同一设备上,以便 Linux 重命名不会失败,并且 Windows 重命名实际上是原子操作。正如 Dave C 在上面提到的那样,创建临时文件(通常使用 ioutil.TempFile)与现有文件位于同一目录中,这是进行原子重命名的方法;这就是我进行原子重命名的方式。

对于我的用例,这对我很有效:

  1. 一个 Go 进程获取更新并重命名文件以交换更新。
  2. 另一个 Go 进程使用 fsnotify 监听文件更新,并在更新时重新映射文件(使用 mmap)。

在上述用例中,仅使用os.Rename 对我而言就已经足够完美了。

更多阅读:

* 原文的 "atomic" 指原子操作,即操作不可分割。

  • rename() 是原子性的吗? "是和否。假设操作系统不崩溃,rename() 是原子性的...."
  • 在 Windows 上是否可能进行原子性文件重命名(带有覆盖)?
  • *注意:我想指出,当人们谈论原子性文件系统 文件 操作时,从应用程序的角度来看,通常意味着操作发生或不发生(这可以通过日志记录来帮助),从用户的角度来看。如果您使用原子内存操作的含义,那么很少有文件系统操作被认为是真正的原子操作(除了直接 I/O [O_DIRECT] 一个块写入和读取,关闭磁盘缓存)。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接