知识卡片
不可变性是并发问题的根治方案,与可变性隔离策略
内容
函数式编程语言里的”变量”是不可变的(Clojure中x一旦初始化就不会再被改变,与Java里随循环递增的可变变量i形成鲜明对比)——这句听起来自相矛盾的话,恰恰点出了不可变性对软件架构的分量:所有的竞争问题、死锁问题、并发更新问题,根源都是可变变量;如果变量永远不会被更改,竞争和并发更新就不可能发生;如果锁状态不可变,死锁就永远不会产生。一切由多线程、多处理器引发的并发问题,如果没有可变变量,本质上都不可能发生。但完全不可变只有在存储和处理能力不受限的理想情况下才彻底可行,现实中通常需要”可变性隔离”:把应用拆成可变和不可变两类组件,不可变组件用纯函数执行任务、期间不改任何状态,需要修改状态时才与非函数式组件通信,而这些真正可变的部分用事务型内存来保护(类似数据库用事务/重试机制保护磁盘数据),例如Clojure的atom机制用比较+替换(CAS)策略:先读取当前值,计算新值后与锁保护的原值比对,一致才写入并释放锁,不一致则重试。这个atom机制只能处理简单场景,遇到多个相关变量同时需要修改引发的并发更新/死锁问题就不够用了,需要更复杂的机制。核心原则是:架构设计良好的应用应该把状态修改的部分和不需要修改状态的部分隔离成独立组件,架构师应该把大部分处理逻辑都推向不可变组件,让可变状态组件里的逻辑越少越好。
参考来源
- 位置:《架构整洁之道》第6章《函数式编程》"不可变性与软件架构""可变性的隔离"(源文件:_epub-src/text/part0011_split_004.html)
- 结论依据:原文说明竞争、死锁、并发更新问题的根源都是可变变量,给出可变性隔离策略(不可变组件用纯函数、可变部分用事务型内存保护)及Clojure atom的CAS机制作为具体实现,并明确架构师应把处理逻辑尽量推向不可变组件,直接支撑本卡片结论。
- 原始内容:所有的竞争问题、死锁问题、并发更新问题都是由可变变量导致的……不可变组件用纯函数的方式来执行任务,期间不更改任何状态……软件架构师应该着力于将大部分处理逻辑都归于不可变组件中,可变状态组件的逻辑应该越少越好。