如何实现"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()
全部抛出。
您的情况的大致工作流程是 - 对于每个文件创建对象:
调用 run()
在正确的目录中创建(并填充?)文件,但使用临时名称 - 最好隐藏
如果失败,则在每个成功 run()
文件创建对象上调用 rollback()
。这必须删除临时文件并且可能不会失败。
然后,放弃,从头重试,提示用户什么的。
- 重复 #1 直到创建完所有文件
- 他们都成功了,所以对每个对象调用
commit()
。这必须将临时文件重命名为其最终名称并且可能不会失败
这要求commit
步骤不能失败。在这种情况下,假设重命名文件不会失败 - 你必须确保这是真的(即没有名称冲突)在你到达 commit
阶段。
请注意确实有中间的非原子变化 - 避免这种情况的唯一方法通常是拥有全局状态的两个副本,以及一个原子交换来提交修改一个。通常,您不能对文件系统执行此操作,即使您正在编写驱动程序也是如此。
使用 Unit of Work EAA 设计模式。
问题
我的程序有一个操作,它由一系列更改全局状态的单个操作组成。每个基本操作都可能失败并使全局状态处于未定义状态。
问题
是否有一种通用模式可以帮助我使组合操作成为“原子的”,即如果其中一个子操作失败,全局状态保持不变?
我使用 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()
全部抛出。
您的情况的大致工作流程是 - 对于每个文件创建对象:
调用
run()
在正确的目录中创建(并填充?)文件,但使用临时名称 - 最好隐藏如果失败,则在每个成功
run()
文件创建对象上调用rollback()
。这必须删除临时文件并且可能不会失败。然后,放弃,从头重试,提示用户什么的。
- 重复 #1 直到创建完所有文件
- 他们都成功了,所以对每个对象调用
commit()
。这必须将临时文件重命名为其最终名称并且可能不会失败
这要求commit
步骤不能失败。在这种情况下,假设重命名文件不会失败 - 你必须确保这是真的(即没有名称冲突)在你到达 commit
阶段。
请注意确实有中间的非原子变化 - 避免这种情况的唯一方法通常是拥有全局状态的两个副本,以及一个原子交换来提交修改一个。通常,您不能对文件系统执行此操作,即使您正在编写驱动程序也是如此。
使用 Unit of Work EAA 设计模式。