c++ - 并发状态文件处理具有多个过程,超出了我们对Linux和Windows的控制范围

标签 c++ linux winapi io

以下问题听起来可能有些漫长而复杂,但是实际上,这是一个在同一文件上运行三个进程的非常简单,通用和常见的问题。在下面的文本中,我试图通过一些说明性示例将问题分解为一组特殊要求。

任务序言

有一个名为索引的文本文件,其中包含一些元数据。

有一个应用程序( APP ),它可以理解文件格式并对其进行有意义的更改。

该文件存储在版本控制系统( VCS )下,该系统是其他用户对同一文件执行的更改的来源。

我们需要设计一个应用程序(APP),该应用程序可以在合理的文件中使用该文件,最好不要与VCS进行过多交互,因为假定使用VCS来保存大型项目,而索引文件只是其中的一小部分。用户可能希望在任何时候更新VCS,而无需考虑APP内正在进行的任何操作。在那种情况下,APP应该以防止任何可能的数据丢失的方式来适当地处理这种情况。

言辞

请注意,未指定VCS,它可能是perforce,git,svn,tarball,闪存驱动器或您最喜欢的基于WWII Morse的收音机和文本编辑器。

文本文件可以是二进制的,不会改变太多。但是考虑到VCS存储,它很容易被合并,因此文本/人类可读格式是最合适的。

此类事情的可能示例是:复杂的配置(AI行为树,游戏对象描述),资源列表,其他不希望手工编辑的,与当前项目有关,但历史很重要的事情。

请注意,除非您热衷于实现自己的版本控制系统,否则将大多数配置“外包”到某些基于客户端-服务器的外部解决方案将无法解决问题-您仍然必须将参考文件保留在版本控制系统中,对数据库中相关配置的匹配版本的引用。这意味着,您仍然有同样的问题,但是规模较小-文件中的单个文本行,而不是一打。

任务本身

处于真空中的通用APP可以分三个阶段进行索引:读取修改写入。读取阶段-读取和反序列化文件,修改-更改内存中状态,写入-序列化状态并写入文件。

此应用程序有三种通用工作流:

  • 阅读-> <提供信息>
  • 读取-> <提供信息并等待用户输入>->修改->写入
  • 读取->修改->写入

  • 第一个工作流程是针对只读的“用户”,例如游戏客户端,它只读取一次数据而忘记了文件。

    第二个工作流程是用于编辑应用程序。由于外部更新很少发生,并且用户不可能同时在几个编辑应用程序中编辑同一文件,因此可以合理地假设一个通用编辑应用程序只希望读取状态一次(尤其是在资源消耗操作),并且仅在外部更新的情况下才重新读取。

    第三个工作流程是用于cli的自动化使用-构建服务器,脚本等。

    考虑到这一点,合理地威胁分别读写和修改。让我们将一个仅进入读取阶段并准备一些信息的操作称为读取操作写操作将是一种修改读取操作的状态并将其写入磁盘的操作。

    由于工作流一和二可能由不同的应用程序实例同时运行,因此允许多个读取操作同时运行也很合理。某些读取操作(如用于编辑应用程序的读取)可能要等到任何现有的写入操作完成后才能读取最新状态。其他读取操作(例如游戏客户端中的读取操作)可能希望读取当前状态,无论它是什么状态,而不会被阻塞。

    另一方面,只有写操作能够检测到任何其他正在运行并中止的写操作才是合理的。写操作还应该检测对索引文件所做的任何外部更改并中止。基本原理-没有必要执行(和等待)任何工作,由于这些工作是基于可能的过时状态而被丢弃的,因此没有意义。

    对于健壮的应用程序,应在应用程序的每个点上假设存在星系标度严重故障的可能性。在任何情况下,这种故障都不应使索引文件不一致。

    要求
  • 文件读取是一致的-在任何情况下,我们都不应在更改文件之前读取文件的一半,而在更改文件之后再读取文件的另一半。
  • 写操作是互斥的-不允许同一文件同时执行其他写操作。
  • 写操作可以可靠地等待-我们应该能够等待写操作完成或失败。
  • 写操作是事务性的-在任何情况下都不应将文件保留为部分更改或不一致的状态,也不要基于过期的状态。应该检测到在操作之前或操作期间对索引文件的任何更改,并应尽快中止操作。
  • 最佳答案

    的Linux

    读操作:

  • 如果需要,请获取共享锁-open(2)(O_CREAT | O_RDONLY)和flock(2)(LOCK_SH)“锁”文件。
  • open(2)(O_RDONLY)索引文件。
  • Create contents snapshot并解析它。
  • close(2)索引文件。
  • 解锁-flock(2)(LOCK_UN)和close(2)“锁定”文件

  • 写操作:
  • 获得排他锁-open(2)(O_CREAT | O_RDONLY)和flock(2)(LOCK_EX)的“锁”文件。
  • open(2)(O_RDONLY)索引文件。
  • fcntl(2)(F_SETLEASE,F_RDLCK)索引文件。 -我们只对那些RDLCK租约感兴趣。
  • 检查状态是否为最新,执行操作,更改状态,并将其写入附近的临时文件。
  • rename(2)索引的临时文件-它是原子的,如果到目前为止还没有租约,我们根本不会-这将是一个不同的文件,而不是我们获得租约的文件。
  • fcntl(2)(* F_SETLEASE,F_UNLCK)索引文件。
  • close(2)索引文件(“旧”文件,在文件系统中没有引用)
  • 解锁-close(2)“锁定”文件

  • 如果收到租约的信号-中止和清除,则不重命名。 rename(2)没有提到它可能会被打断,而POSIX requires却是原子的,因此一旦我们了解它就可以了。

    我知道有共享内存互斥锁和命名信号量(而不是应用程序实例之间进行协作的建议性锁定),但是我认为我们都同意,它们对于手头的任务来说不必要地复杂,并且有自己的问题。

    视窗

    读操作:
  • (如果需要)获取共享锁-CreateFile(OPEN_ALWAYS,GENERIC_READ,FILE_SHARE_READ)和LockFileEx(1字节)“锁定”文件
  • CreateFile(OPEN_EXISTING,GENERIC_READ,FILE_SHARE_READ)索引文件
  • 读取文件内容
  • CloseHandle索引
  • 解锁-CloseHandle“锁定”文件

  • 写操作:
  • 获得排他锁-CreateFile(OPEN_ALWAYS,GENERIC_READ,FILE_SHARE_READ)和LockFileEx(LOCKFILE_EXCLUSIVE_LOCK,1字节)“锁定”文件
  • CreateFile(OPEN_EXISTING,GENERIC_READ,FILE_SHARE_READ | FILE_SHARE_WRITE)索引文件
  • 索引文件目录上的
  • ReadDirectoryChanges(错误,FILE_NOTIFY_CHANGE_LAST_WRITE),具有OVERLAPPED结构和一个事件
  • 检查状态为最新。修改状态。写一个临时文件
  • Replace the index file with a temporary
  • CloseHandle索引
  • 解锁-CloseHandle“锁定”文件

  • 在修改过程中,使用WaitForSingleObject(零超时)检查OVERLAPPED结构中的事件。如果存在索引事件-终止操作。否则-再次启动手表,检查我们是否仍是最新的;如果是,则-继续。

    备注
  • Windows版本使用锁定而不是Linux版本的通知机制,这可能会干扰外部进程进行写操作,但是Windows中似乎没有其他方法。
  • 关于c++ - 并发状态文件处理具有多个过程,超出了我们对Linux和Windows的控制范围,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32534319/

    相关文章:

    c - 在 C/C++ 中执行 linux 程序并通过 stdin/stdout 与其通信的最简单方法

    c - windows xp下如何监控windows服务的状态变化?

    C++ Win32 背景图像

    c++ - 如何在不定义目标变量大小的情况下将字符串变量逐个字符复制到另一个字符?

    c++ - 用于即时提取 zip 的库

    linux - 无法在 linux 14.04 上安装 nvm

    linux - DISTINCT 子句不适用于 linux 上的 odbc_exec

    c++ - 海森堡 : WinApi program crashes on some computers

    c++ - 用枚举填充 QMenu

    c++ - 在原始图像中的组件周围显示矩形