假设我有两个线程:第一个是使用pwrite
将数据块写入文件,第二个是使用pread
从中读取字节。我们做两个假设:
1)读取线程可以读取文件的一个字节,当且仅当写入线程已经写入了它时。
2)编写线程永远不会将不同的数据写入文件的同一个字节,但它偶尔会重写之前编写的完全相同的数据。
假设读取线程在写入线程重写的同时读取一些数据。阅读线程最终是否可能会收到损坏的数据?
答案 0 :(得分:0)
我不知道在您描述的条件下对读/写原子性的任何实际要求。
首先,从POSIX write
standard开始,有以下两个要求:
在常规文件或其他能够搜索的文件上,实际写入 数据应从文件中指明的文件中的位置开始 与fildes相关的文件偏移量。在成功返回之前 write(),文件偏移量应增加字节数 实际写的。在常规文件中,如果是最后一个字节的位置 写入大于或等于文件的长度,长度 该文件应设置为此位置加一。
并附加到文件是 atomic:
如果设置了文件状态标志的O_APPEND标志,则为文件偏移量 应在每次写入之前设置为文件的末尾,否则 干预文件修改操作应在更改之间进行 文件偏移和写操作。
虽然这并不是所有写入调用的原子性的明确要求,但我无法想象现实世界的文件系统如何以一种实际的方式实现满足这些要求,而不是隐含地提供原子写调用。如果数据的传输和文件偏移的更新必须是组合的原子操作,那么强意味着传输本身必须是原子的。
现在,添加显式要求:
对管道或FIFO的写请求应以与a相同的方式处理 常规文件,但有以下例外:
...
- {PIPE_BUF}字节或更少字节的写请求不应与来自在同一管道上执行写操作的其他进程的数据交错。大于{PIPE_BUF}个字节的写入可能在任意边界上交错数据,并与其他进程进行写入,无论是否设置了文件状态标志的O_NONBLOCK标志。
这似乎符合您的需求,但仅适用于其他写操作。根本没有提到同时读取操作。并且请注意,即使PIPE_BUF
原子写入要求受限于"也不应与来自其他进程的数据交错#34;。
我没有意识到任何读取操作必须"参见"原子地写入数据。 POSIX read standard州
标准开发人员考虑将原子性要求添加到a 管道或FIFO,但由于管道和FIFO的性质而认识到 无法保证{PIPE_BUF}或任何内容的读取的原子性 其他大小有助于应用程序的可移植性。
和
I / O旨在成为普通文件和管道以及FIFO的原子。 原子意味着从单个操作开始的所有字节 一起结束,不与其他I / O交错 操作。这是终端的已知属性,但事实并非如此 荣幸,终端明确(并且隐含永久) 例外,使行为未指定。其他的行为 设备类型也未指定,但措辞旨在 意味着未来的标准可能会选择指定原子性(或 不是)。
因此,显然没有要求读操作的原子性。
但现实世界的实施再次发挥作用。磁盘是块设备。磁盘上的文件以块的形式分配。页面缓存采用页面大小的块。我无法看到对任何特定数据块执行写操作的任何方法,这些数据块不需要在更新该块中的数据时锁定该块。因此,只要您保持在单个页面缓存块内(假设正常的缓存IO操作),我就冒昧地说原子性几乎肯定会得到保证。
但是您依赖于实施细节 - 即使这样,只有当您停留在页面缓存中的单个页面中时才会这样做。跨页边界甚至实现细节可能无法在写入/读取周期内提供任何原子性尝试。
由于这些实现细节,您可能观察原子行为 - 假设小到中等大小的更新,因此不太可能跨越页面边界。即便如此,跨越页面边界将按顺序完成,并且当更新从页面A跨越到页面B时,锁定页面A的第一个线程也可能是锁定页面B的第一个线程。没有保证锁定页面A和B以进行挂起写入操作会阻止对页面B的读取访问,同时只有页面A正在被主动更新 - 它再次依赖于实现细节 - 它可能工作。
所以我无法保证能够看到。
以这种方式看待:在内存中读取和写入诸如unsigned long
之类的简单值并不保证是原子的。
这甚至无法解决读写操作无法首先传输所有请求数据的事实。