为什么文件在被另一个进程写入时有时会显示为空

为什么文件在被另一个进程写入时有时会显示为空

我们遇到过这样的情况:文件通过 FTP 上传到文件夹,然后由 nginx 提供服务。我们发现,如果在修改文件后立即发送 GET 请求,nginx 将返回 0 字节的文件。

在尝试调试这个问题时,我编写了 2 个 python 脚本来查看是否能以简单的方式重现该错误。

第一个写入文件

  while True:
      with open('testfile' , 'w') as f:
          f.write("test")

第二句是

  while True:
      with open('testfile' , 'r') as cf:
          print(cf.read())

当在两个单独的进程中运行这些文件时,读取器的输出要么是“test”,要么是“”,这表明有时文件对读取器来说似乎是空的。这似乎与 python 实现无关,因为我可以用 bash 重现这种效果,如下所示:

(writer.sh)

  while true; do
      echo test > testfile
  done

(读者.sh)

  while true; do
      cat testfile
      printf "\n"
  done

文件系统是ext4,操作系统是Ubuntu 16.04。

所以:

为什么读者有时会看到一个空文件(大约 50% 的时间)?

为什么我们从来没有看到部分写入(“te”,“tes”等)?

提前感谢您的帮助。

答案1

太棒了,你刚刚发现了文件缓冲。写入磁盘时,你可以使用缓冲写入或直接 I/O 写入。出于性能原因,大多数软件(包括 Python 解释器)默认使用缓冲写入。如果你需要执行直接 I/O,有一个很好的 Python 模块,名字恰如其分方向就是这样的。

然而大多数时候您不需要直接 I/O,除非您正在写入某些日志文件或数据库。

答案2

其他人已经描述了这是如何缓冲 I/O,您可以在其内容被刷新之前看到被截断的文件。

关于解决此问题的几种方法的更多详细信息:

将文件上传到与目标相同的文件系统上的临时目录,然后 mv 到位。重命名是一个原子操作,因此读者只会看到旧文件或新文件,而不会看到介于两者之间的文件。但是,除非应用程序调用 fsync(),否则内核仍会按计划完成对磁盘的写入。关闭文件或等待任意时间不会不是可靠地使文件存在于磁盘上。

或者,将应用程序更改为由数据库支持。让数据库在内存和存储中提供文档的一致视图,这就是它们的作用。如果唯一的原因是为了摆脱非常小的不一致窗口,那么实施努力可能不值得。

答案3

您可能会遇到以下竞争条件:

  • 由于重定向(“>”),写入会截断文件。
  • 该文件被读取器读取(空文件)。
  • 该文件是由作者编写的。

如果在写入循环中设置短暂的睡眠时间,则会出现更少的这种情况。

您可以使用原子操作创建文件来避免这种情况,例如:

while true do;
    echo test > file.tmp
    mv file.tmp testfile
done

您的原始代码将不断截断并写入同一个文件。上面的循环将不断创建新文件。该mv命令是原子的,读取器将始终看到一个包含数据的文件。这将是被删除的文件mv或新文件。

答案4

文件通常以块(完整数据的子集)的形式写入,这些块的大小由所使用的功能和可用的系统资源共同决定,因此操作系统通常会尝试优化块大小。实际情况是,块在写入磁盘之前会先写入 RAM,然后整个块会一次性写入磁盘,在写入该块期间没有时间进行读取。这导致写入速度比其他方式快得多。

在您的情况下,写入单词“test”将小于操作系统选择的任何块大小,因此它将一次性全部写入。对于您的测试,您应该编写一个更大的测试,并可能设置块大小(尽管大多数情况下最好让操作系统决定)。

我怀疑测试中发生的情况是,一半时间是在写入之前捕获空文件,另一半时间是在写入块之后捕获空文件。如果您尝试写入大于块大小的数据量,我认为您会看到部分写入的文件。

相关内容