如何实现"atomicity"一系列操作? (不一定与多线程相关)

How to implement "atomicity" for a series of operations? (not necessarily multithreading related)

问题

我的程序有一个操作,它由一系列更改全局状态的单个操作组成。每个基本操作都可能失败并使全局状态处于未定义状态。

问题

是否有一种通用模式可以帮助我使组合操作成为“原子的”,即如果其中一个子操作失败,全局状态保持不变?

我使用 C++,所以如果答案包含代码,请选择该语言(如果您有选择的话)。但我不介意其他语言的示例。

评论

我的想法

我能想出的最好办法是 class 获取操作列表及其逆操作。如果任何操作失败,它会执行已执行操作的反向操作。但是如果其中一个反向操作失败怎么办?

class 的界面会是什么样子?我会有一个类似于可逆操作的额外 class 吗?你知道我可以在哪里阅读更多关于这个问题的信息吗?

当我第一次读到你的问题时,我想象的是这样的:

bool ApplyStateTransitions(Transitions& transitions) {
   auto lock = BlockUntilLocked(); // begin critical section
   auto clone = mCurrentState.Clone(); // copy all the state
   auto success = clone.ExecuteTransitions(transitions);
   if (!success)
      return false;
   mCurrentState = clone;
   return true;
}

您可以对 immutable 状态模式执行类似的方法。

通常在状态机中复制当前状态并不昂贵;他们只是没有那么多州。或者它更像是一个数据库 table?

数据库引擎使用带有回滚或提交方法的事务。您可以在此处了解该方法:https://en.wikipedia.org/wiki/ACID

基本解决方案是提交或回滚。

通用 C++ 实现是构建支持 3 种方法的对象列表:

  • run() 将执行所有 可能 失败的步骤,而不会产生 可见的 副作用。如果失败
  • ,它必须抛出而不改变任何东西
  • commit() 可能不会抛出,并且只能使 run() 期间执行的步骤可见
  • rollback() 可能不会抛出,并且只能撤消 run()
  • 期间执行的步骤

然后迭代该列表,对所有内容调用 run()。如果有任何抛出,抓住它并 rollback() 一切 运行 成功。如果 none 抛出,commit() 全部抛出。


您的情况的大致工作流程是 - 对于每个文件创建对象:

  1. 调用 run() 在正确的目录中创建(并填充?)文件,但使用临时名称 - 最好隐藏

    • 如果失败,则在每个成功 run() 文件创建对象上调用 rollback()。这必须删除临时文件并且可能不会失败。

      然后,放弃,从头重试,提示用户什么的。

  2. 重复 #1 直到创建完所有文件
  3. 他们都成功了,所以对每个对象调用 commit()。这必须将临时文件重命名为其最终名称并且可能不会失败

这要求commit步骤不能失败。在这种情况下,假设重命名文件不会失败 - 你必须确保这是真的(即没有名称冲突)你到达 commit阶段。


请注意确实有中间的非原子变化 - 避免这种情况的唯一方法通常是拥有全局状态的两个副本,以及一个原子交换来提交修改一个。通常,您不能对文件系统执行此操作,即使您正在编写驱动程序也是如此。

使用 Unit of Work EAA 设计模式。