【问题标题】:Multi-processing and file operations?多处理和文件操作?
【发布时间】:2013-02-14 11:57:37
【问题描述】:

在基于 Windows 的操作系统中,假设有几个不同的进程可以通过使用 fopen/fopen_s/fwrite 等频繁地读取和/或写入文件,在这种情况下,我是否需要考虑数据竞争,或者操作系统可以处理这会自动确保文件只能在任何给定时间由单个进程打开/更新,而其余的 fopen 尝试将失败?那么在这件事上基于 linux 的操作系统呢?

【问题讨论】:

  • 顺便说一句,在这种情况下,我建议使用 OS api 而不是标准 c 或 c++ 库,因为标准 c 和 c++(不包括 c++11)与多处理或多线程无关。
  • @AliVeli 这并不完全正确:ios_base::app"a" 选项旨在尽可能地是原子的,因此将多个进程附加到同一个文件应该可以工作。然而,还有更多,我同意——系统级调用更合适。 (特别是,如果您需要锁定,这只能通过系统级调用实现。)

标签: c++ c windows multiprocessing


【解决方案1】:

在 Windows 中,这取决于您打开文件的方式。

OpenFile 的情况下查看uStyle 参数的一些可能值,在CreateFile 的情况下查看dwShareMode

请注意OpenFile 已被弃用,但最好使用CreateFile

【讨论】:

  • 如果他使用 iostream 或 FILE* 函数,他对于 dwShareMode 参数没有任何选择。从 QoI 的角度来看,我希望 FILE_SHARE_READFILE_SHARE_WRITE (但我的观点可能受到我的 Unix 背景的影响,你没有这个论点,open 就像所有FILE_SHARE_... 选项已设置)。
  • @James Kanze 不错,我没有考虑到他/她想使用 iostream 或 cstdio 函数。然后我在原始问题下对此发表了评论。
  • @AliVeli 除非他在做一些简单的事情(比如写作时总是追加),否则可能是系统级函数更合适。 (但我认为 OpenFile 已被弃用,应该始终在新代码中使用 CreateFile。)
  • MSDN 关于OpenFile 声明如下: 注意此函数功能有限,不推荐使用。对于新的应用程序开发,请使用CreateFile 函数。所以,确实如此。
【解决方案2】:

您必须注意不要同时从多个线程打开同一个文件 - 因为完全有可能多次打开该文件,并且操作系统可能会也可能不会按照您的预期执行,具体取决于您打开的模式中的文件 - 例如如果您创建一个新文件,它肯定会创建两个不同的文件(其中一个在关闭时会消失,因为它已被另一个线程删除,太好了,嗯?)。规则非常复杂,最糟糕的是,如果你不加倍小心,你会得到“混合输出到同一个文件”——所以行甚至部分行会从两个线程中混合。

即使操作系统阻止您两次打开同一个文件,您仍然需要处理“FILE *NULL返回”的后果。那你怎么办呢?回去再试一次,还是失败,还是?

我不确定我能否就如何解决这个问题提出一个好的建议,因为你没有很好地描述你对这些文件做了什么。我想到了一些不同的事情:

  1. 保留一个文件名“寄存器”,以及为能够打开文件而必须保留的每个文件的互斥锁。
  2. 使用单个“文件线程”读取/写入文件数据,然后将“我想将这些内容写入文件 aa.txt”进行排队,然后让工作人员在执行过程中进行写入。
  3. 使用较低级别的文件系统调用,并使用“独占”访问文件,在发生冲突时使用某种“退避”行为。

我相信还有很多其他方法可以解决这个问题——这真的取决于你想要做什么。

【讨论】:

  • 我现在要做的是:(1)进程#1(不是线程)负责数据的更新(2)进程#2只会读取数据更新通过流程#1,但是我需要确保流程#2 将只使用完全更新的数据以避免新旧数据之间的任何混淆,我认为至少对于 Windows,我不需要做任何事情来避免数据竞争案例?
  • “如果你创建一个新文件,它肯定会创建两个不同的文件”——fopen 是不可能的,而且整个“在其他线程中删除”的场景在 Windows 上是不可能的。也许不是对整个答案投反对票的理由,因为这件事确实很复杂,而且您的一些解决方案很有意义,但是...
  • 在这种情况下,您必须实现一个消费者-生产者解决方案来通知读取过程它可以读取数据。例如,您可以使用窗口事件。 (CreateEvent、SetEvent 等...)
  • @user0002128 对于系统级 WriteFile 来说是这样; fwriteostream::write 不一定如此。如果 1) 你确保流有一个足够大的缓冲区用于任何单次写入,并且在每次写入后刷新。
  • @James Kanze:是的,现在我知道让 OS API 处理这个问题会更好、更简单。
【解决方案3】:

也许吧。如果您在谈论不同的流程(而不是 线程),适用于的常规数据竞争条件 线程不适用。但是(并没有区别 在 Unix 和 Windows 之间):

  • 任何单个write/WriteFile 操作都是原子操作。 (我是 不是 100% 确定关于 Windows,但我无法想象 否则。)但是,如果您使用的是 iostream 或更旧的版本 FILE* 函数,您无法直接控制这些函数何时使用 进行操作。通常,它们只会在 流的缓冲区已满。您需要确保缓冲区 足够大,并在每次输出后显式刷新。 (如果 您正在输出合理长度的行,例如 80 最多字符,可以肯定的是缓冲区将 保持完整的路线。在这种情况下,只需使用std::endl 即可 终止 iostreams 中的行;对于 C 风格的函数, 您必须先致电setvbuf( stream, NULL, _IOLBF, 0 ) 第一个输出。

  • 进程中的每个打开文件都有自己的想法 写入文件,以及它自己对文件末尾位置的想法。 如果您希望所有写入都转到文件末尾,则需要 在 C++ 中使用 std::ios_base::app 打开它,或者在 C 中使用 "a" 打开它。只需 std::ios_base::out/"w" 还不够。 (另外,当然, 只有std::ios_base::out 或“w”,文件将是 打开时被截断。有几个不同的过程 截断文件可能会导致数据丢失。)

  • 读取其他进程正在写入的文件时:当 您到达文件末尾,流或FILE 出错 状态,并且不会尝试进一步阅读,即使其他进程 正在附加数据。在 C 中,clearerr 应该(我认为)撤消 这个,但不清楚接下来会发生什么;在 C++ 中,清除 流中的错误并不一定意味着进一步读取 也不会立即遇到文件结尾。同时 情况下,最安全的选择是记住每次之前你在哪里 读取,如果读取失败,关闭文件,然后重新打开 它,寻找你所在的地方,然后从那里开始阅读。

  • 随机访问,写入文件末尾以外的地方,将 也可以工作,只要所有写入都是原子的(见上文);你 应该始终获得一致的状态。如果你写的取决于 然而,关于你所阅读的内容,以及其他进程正在做的事情 类似的东西,你需要文件锁定,这不是 在 iostream/FILE* 级别可用。

【讨论】:

  • windows 真的保证写入是原子的吗?
  • @MatsPetersson 希望如此。我从来没有真正弄清楚 Windows 能保证什么,不能保证什么。但通常,系统调用应该是原子的。 (尽管通常情况下,即使在 Unix 下,也有一个最大大小,超过该大小不再保证写入是原子的。)
  • @MatsPetersson 我已经检查过了。如果写入的大小小于磁盘扇区的大小,Windows 会保证原子性。 (旧的 Unix 保证它最高可达 4K。奇怪的是,我在 Posix 中找不到任何保证,除非文件描述符是管道。)
猜你喜欢
  • 2020-12-02
  • 1970-01-01
  • 2019-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多