如果 2 个不同的进程同时在同一个文件上调用 write 系统调用会发生啥

Posted

技术标签:

【中文标题】如果 2 个不同的进程同时在同一个文件上调用 write 系统调用会发生啥【英文标题】:What happens if a write system call is called on same file by 2 different processes simultaneously如果 2 个不同的进程同时在同一个文件上调用 write 系统调用会发生什么 【发布时间】:2011-11-06 09:07:10 【问题描述】:

操作系统是否正确处理它?

或者我必须调用flock()吗?

【问题讨论】:

什么是“正确”?你预计会发生什么? Question with similar answer. 【参考方案1】:

虽然操作系统不会崩溃,文件系统也不会损坏,但对write() 的调用保证是原子的,除非相关文件描述符是管道,并且要写入的数据量为PIPE_MAX 字节或更少。 the standard的相关部分:

尝试写入管道或 FIFO 有几个主要特征:

原子/非原子:如果在一个操作中写入的全部量未与来自任何其他进程的数据交错,则写入是原子的。当有多个写入器向单个读取器发送数据时,这很有用。应用程序需要知道可以以原子方式执行的写入请求有多大。此最大值称为 PIPE_BUF。本卷 IEEE Std 1003.1-2001 并未说明超过 PIPE_BUF 字节的写入请求是否是原子的,但要求 PIPE_BUF 或更少字节的写入应该是原子的。

[...]

因此,原则上,您必须同时锁定写入者,否则您写入的数据可能会混淆和乱序(即使在同一写入中),或者您可能有多个写入相互覆盖。但是,有一个例外 - 如果您通过 O_APPEND,您的写入将是有效的原子:

如果设置了文件状态标志的 O_APPEND 标志,则文件偏移量应在每次写入之前设置为文件末尾,并且在更改文件偏移量和写入操作之间不应发生中间文件修改操作。

虽然这对于非O_APPEND 写入或同时读取而言不一定是原子的,但如果所有写入者都使用O_APPEND,并且您在执行read 之前以某种方式同步,那么您应该没问题。

【讨论】:

我不认为O_APPEND 使写入原子化。您的所有引用都说写入将始终发生在文件末尾。使用O_APPEND 同时写入的两个进程不会破坏彼此的写入,但它们仍然可以交错出现。 @R..,这是真的,但如果两个进程各自发出 single 写入 PIPE_BUF 字节或更少字节到使用 O_APPEND 打开(两端)的同一文件,则不会交错写入 O_APPEND 文本与管道无关。管道总是类似附加的。该文本是关于普通文件的,PIPE_BUF 文本不适用于普通文件。 @R..,我认为 PIPE_BUF 无关紧要,但在实践中,后一个引用意味着写入必须在干预写入发生之前完成。将其解释为“在更改文件偏移量和写入至少一个字节之间不应发生干预修改操作”有点迂腐,而且我认为我从未见过这样的系统就这样。 请记住,write 可以被信号中断(包括不可阻塞的 SIGSTOP),虽然这不会给出 EINTR,除非你设置了一个非重启的信号处理程序,它会导致写入返回,如果它已经完成了部分写入。因此,您无法确保原子性。即使没有信号,writeO_APPEND 也应该被视为由 N 较小的写入组成,每个写入都遵循查找和写入之间的原子性条件,但相对于彼此。【参考方案2】:

write(以及writev)保证原子性。

这意味着如果两个线程或进程同时写入,您无法保证哪个线程或进程先写入。 但是您确实可以保证,一个系统调用中的任何内容都不会与另一个系统调用中的数据混合。

只要它总是正确地工作,但不一定按照你期望的方式(如果你假设进程 A 在进程 B 之前)。

【讨论】:

我唯一的要求是,如果 Thread-A 写了一堆行(比如 lines-A),而 thread-B 写了一组行(比如 lines-B),那么输出要么是整行-A 后跟行-B 或整行-B 后跟行-A。我不希望半行-A 写在行-B 之间。只要 line-A 和 lines-B 完全提交,而其他线程不会在两者之间转储其输出,我不在乎。我不关心行-A 和行-B 之间的相对排序的顺序。 (A 行,B 行)是可以接受的。 (lines-B,lines-A) 也可以接受 writewritev 保证原子性仅适用于写入小于 PIPE_MAX 的管道或 FIFO,我相信 - 除非您另有引用? @bdonlan: 例如writev 的写法(没有搜索write 的写法):"readv() 和 writev() 执行的数据传输是原子的:由 writev() 写入的数据被写入为单个块,不会与其他进程中写入的输出混合(但请参阅 pipe(7) 了解异常)" @ajay:您可以根据许多无法立即判断的因素(IO 优先级、进程优先级、信号和系统调用重启、宇宙辐射)获得 A 和 B 行的任何可能组合,但是你永远不应该(假设没有内核/驱动程序错误)得到半 A 线和半 B 线或其他一些垃圾。 手册页说:“请注意,成功调用传输比请求的字节少的不是错误”。如果进程 A 进行部分写入,则其数据可能与来自进程 B 的数据混合。【参考方案3】:

当然,内核会正确处理它,因为内核的正确性理念——根据定义,它是正确的。

如果您有一组合作的植绒机,那么您可以使用内核将每个人排队。但请记住,flock 与 I/O 无关:它不会阻止其他人写入文件。最多只会干扰其他的植绒者。

【讨论】:

【参考方案4】:

是的,它当然会正常工作。它不会使操作系统或进程崩溃。

这是否有意义,取决于应用程序的编写方式以及文件的用途。

如果所有进程都以仅追加方式打开文件,则每个进程(理论上)在每次写入之前都会执行一次原子搜索结束;这些保证不会覆盖彼此的数据(当然,顺序是不确定的)。

在任何情况下,如果您使用的库可能会将单个逻辑写入拆分为多个写入系统调用,则可能会遇到麻烦。

【讨论】:

【参考方案5】:

write()writev()read()readv() 可以在传输的数据量小于请求的数据量时生成部分写入/读取。

引用writev() 的 Linux 手册页:

请注意,成功调用传输的字节数少于请求的字节数并不是错误

引用 POSIX 手册页:

如果write()在成功写入一些数据后被信号中断,它应该返回写入的字节数。

AFAIU, O_APPEND 在这方面没有帮助,因为它不会阻止部分写入:它只确保写入的任何数据都附加到文件末尾。

看到这个bug report from the Linux kernel:

一个进程正在将消息写入文件。 [...] 写入 [...] 可以一分为二。 [...] 因此,如果信号到达 [...],写入将被中断。 [...] 就规范(POSIX、SUS、...)而言,这是完全有效的行为

FIFO 和 PIPE 写入小于 PIPE_MAX,但保证是原子的。

【讨论】:

以上是关于如果 2 个不同的进程同时在同一个文件上调用 write 系统调用会发生啥的主要内容,如果未能解决你的问题,请参考以下文章

在具有 2 个不同 .SVC 文件的多线程环境中调用 WCF 服务。同时调用两个服务时出错

DLL能被几个进程同时调用吗?

当 2 个进程尝试同时在不同管道上相互通信时,使用命名管道的 WCF IPC 会崩溃

用户模式和内核模式:同时使用不同的程序

Scrapy运行2个蜘蛛,使用一个进程将输出到2个不同的文件(AWS Lambda)

ffmpeg同时输出2个不同的音频文件到不同的输出设备