如何为一系列操作实现“原子性”? (不一定是多线程相关的)

时间:2015-11-05 15:25:00

标签: design-patterns atomic

问题

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

问题

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

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

评论

  • 这类似于数据库的“原子性”,在执行提交时,您可以在其中添加全部或全部内容。这是如何实现的?

  • 在我的情况下,我的全局状态是文件系统的状态。我需要一次添加或删除多个文件,并希望确保它们都已添加或未添加任何内容。

我的想法

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

该类的界面如何?我是否会有一个类似于可逆操作的额外课程?你知道一个我可以阅读更多关于这个问题的地方吗?

3 个答案:

答案 0 :(得分:2)

基本解决方案是commit-or-rollback。

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

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

然后遍历该列表,在所有内容上调用run()。如果有任何抛出,请抓住它并rollback()成功运行的所有东西。如果没有,则commit()全部抛出。

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

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

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

      然后,放弃,从头开始重试,提示用户或其他什么。

  2. 重复#1,直到您创建了所有文件
  3. 他们都成功了,所以在每个对象上调用commit()。这必须将临时文件重命名为其最终名称,并且可能不会失败
  4. 这要求commit步骤不会失败。在这种情况下,假设重命名文件不会失败 - 你必须确保这是真的(即,没有名称冲突)之前你到达commit阶段。

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

答案 1 :(得分:1)

当我第一次阅读你的问题时,我想到了这样的事情:

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;
}

您可以使用不可变状态模式执行类似的方法。

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

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

答案 2 :(得分:1)

使用Unit of Work EAA设计模式。