知识卡片
并发的两个本质问题:更新丢失与不一致读
内容
并发控制真正要防范的本质问题主要有两类。更新丢失最容易理解:David和Martin同时编辑同一份文件的不同部分,David先开始却先完成、Martin后完成,Martin提交时读的是David修改前的旧版本,写回时就把David的修改整个覆盖掉,David的更新凭空消失了——这类问题的根源是”读的时候没看到别人已经写进去的东西,写的时候又把这个没看到的更新覆盖了”。不一致读则更容易被忽略:Martin先查了A包有7个类,中途接了个电话,这期间David往A包加了2个类、往B包加了3个类(原本5个类),Martin挂电话后接着查B包看到8个类,两个数字相加得到15——但15永远不是正确答案,正确答案只能是David修改前的12(两次查询都发生在修改前)或修改后的17(两次查询都发生在修改后),因为Martin读到的是两个分别正确但互相矛盾的时间切片拼出来的结果。这两类问题都属于正确性(安全性)层面的失败:只要保证同一时刻只有一人能操作数据,两者都不会发生,但这样做会严重削弱并发的灵活性——因此并发设计从来不是单纯追求正确性,而是要在正确性与灵活性之间根据失败的严重性和实际并发需求做权衡。
结构图:
flowchart TB
A["并发本质问题"]
A --> B["更新丢失<br/>后完成的写覆盖了先完成的写"]
A --> C["不一致读<br/>读到的是跨越不同时间点的数据拼凑结果"]
B --> D["根源:写之前没重新读最新状态"]
C --> E["根源:两次读之间数据被改动,结果既非改前也非改后"]
参考来源
- 位置:《企业应用架构模式》第一部分"表述"之"第5章 并发"之"5.1 并发问题"(源文件:_epub-src/OEBPS/Text/000030.html)
- 结论依据:原文用Martin和David同时编辑源代码文件的案例分别演示"更新丢失"("Martin读的文件并没有包括David的更新,因此当Martin写入文件时,就会覆盖David更新过的那个版本,David的更新就永远丢失了")与"不一致读"("正确的答案应该是David更新前的12个类或者是David更新后的17个类……然而15个类永远都不会是正确的"),直接支撑本卡结构图。
- 原始内容:这个问题称为不一致读,因为Martin读取的数据是不一致的……上述两个问题都会导致正确性(或安全性)的失败,从而产生错误的行为。